単純なループを探していた方法

ライブラリアイコン私は午後遅くに起きて、それを決定しました。それはいつでも、ライブラリに新しい機能を作成するときです。1つは、グラフを確認して、修正してスピードアップするサイクルを確認します。朝の昼までに、私は新機能を作り、コードを改善し、グラフ表現を便利な形にして、グラフ内のすべての単純なサイクルを見つけるという問題に取り組みました。一杯の冷たい水をすすりながら、私はグーグルを開いて、「グラフ内のすべての単純なサイクルの検索」と入力して、見ました...



あまり見えませんでしたが…朝は4時くらいでした。アルゴリズムへのリンクのカップルのリンク1リンク2、および多くのヒントこと寝る時間ですグラフには多くのサイクルが存在する可能性があり、一般的な場合、問題は解決できません。そして、このトピックに関するハブレに関する別の質問、その答えも私を救いませんでした- 私はすでに詳細な検索について知っいました。



しかし、私が解決したら、NPでもPの難しい問題を解決します。コーヒーではなく水を飲むほど、それは突然終了することはありません。そして、私は自分のタスクのフレームワーク内で、スーパーコンピュータなしですべてのサイクルを短時間で見つけることができるはずだと知っていました。



探偵から少し離れて、なぜそれが必要なのかを理解しましょう。



このライブラリとは何ですか?



DITranquilityと呼ばれるライブラリはSwiftで記述されており、そのタスクは依存関係を注入することです。このライブラリは、依存関係の挿入というタスクを強打で処理し、他のSwiftライブラリでは実行できない機能を備えており、速度も優れています。



しかし、なぜディペンデンシーグラフのループをチェックする習慣を身に付ける必要があるのでしょうか。



killerfichiのために-ライブラリが強打で主要な機能を実行する場合、それを改善してより良くする方法を探しています。致命的とは、依存関係グラフの正確性をチェックすることです。これは、実行時の問題を回避できるようにする一連の異なるチェックであり、開発時間を節約します。また、サイクルのチェックは、すべてのチェックで個別に際立っています。これは、この操作にかなり時間がかかるためです。そして最近まで、より多くの非文明化されていました。



, , , . , "Graph API" . "Graph API" — , :



  • - . , , , .
  • , - — graphvis.
  • .


— , ...







, :



  • MacBook pro 2019, 2,6 GHz 6-Core i7, 32 Gb, Xcode 11.4, Swift 5.2
  • Swift 300+ ( )
  • 900
  • 2000
  • 40
  • 7000


debug , release, debug.



, 95 .



3 , .



1.



. , .

, . Graph API , , . . , , , .



, , — . , , , :



  • — , .
  • — ,


, , , . , — , . — ?



, - :



Graph:
    vertices: [Vertex]
    adjacencyList: [[Edge]]

Vertex:
    more information about vertex

Edge:
    toIndices: [Int]
    information about edge


Vertex , Edge , , .

, . , , , , .



2.



, 4 , , , , , — " , , ". :



func findAllCycles() -> [Cycle] {
  result: [Cycle] = []
  for index in vertices {
    result += findCycles(from: index)
  }

  return result
}

func findCycles(from index: Int) -> [Cycle] {
  result: [Cycle] = []
  dfs(startIndex: index, currentIndex: index, visitedIndices: [], result: &result)

  return result
}

func dfs(startIndex: Int,
         currentIndex: Int,
         // visitedIndices   
         visitedIndices: Set<Int>,
         // result   -  
         result: ref [Cycle]) {
  if currentIndex == startIndex && !visitedIndices.isEmpty {
    result.append(cycle)
    return
  }

  if visitedIndices.contains(currentIndex) {
    return
  }

  visitedIndices += [currentIndex]

  for toIndex in adjacencyList[currentIndex] {
    dfs(startIndex: startIndex, currentIndex: toIndex, visitedIndices: visitedIndices, result: &result)
  }
}


, 10 … , , — - ...



, — ? , ? , dfs , 30 . 900 * 1000000 = 900.000.000 dfs…



? , , … ZZzzz...



3.



, , , , , - … , , , !



, , , , . — , , . , , , - :



func findAllCycles() -> [Cycle] {
  globalVisitedIndices: Set<Int> = []
  result: [Cycle] = []
  for index in vertices {
    if globalVisitedIndices.containts(index) {
      continue
    }
    result += findCycles(from: index, globalVisitedIndices: &globalVisitedIndices)
  }

  return result
}

func findCycles(from index: Int, globalVisitedIndices: ref Set<Int>) -> [Cycle] {
  result: [Cycle] = []
  dfs(currentIndex: index, visitedIndices: [], globalVisitedIndices, &globalVisitedIndices, result: &result)

  return result
}

func dfs(currentIndex: Int,
         // visitedIndices   
         visitedIndices: Set<Int>,
         // globalVisitedIndices   -  
         globalVisitedIndices: ref Set<Int>,
         // result   -  
         result: ref [Cycle]) {

  if visitedIndices.contains(currentIndex) {
    //  visitedIndices ,   ,     
    result.append(cycle)
    return
  }

  visitedIndices += [currentIndex]

  for toIndex in adjacencyList[currentIndex] {
    dfs(currentIndex: toIndex, visitedIndices: visitedIndices, globalVisitedIndices: &globalVisitedIndices, result: &result)
  }
}


" , " … . 10 , , , , :



  • , , , .
  • "" — , .


, . , , .



4.



, . , , , , , , . , ? . , , run… , — , … , … … , . :



if visitedIndices.contains(currentIndex) {


, , . :





B->E->C , if . , :

A->B->E->C->B!.. C, . A->B->D->A.

A->C->B->D->A , C .



, dfs , .



5.



, . , , , dfs 30 , 1-2 . :





"Big" - , A.



! Big C, , A B, , C , , A.



? , , , . . O(N^2) , .



, :



func findAllCycles() -> [Cycle] {
  reachableIndices: [Set<Int>] = findAllReachableIndices()
  result: [Cycle] = []
  for index in vertices {
    result += findCycles(from: index, reachableIndices: &reachableIndices)
  }

  return result
}

func findAllReachableIndices() -> [Set<Int>] {
  reachableIndices: [Set<Int>] = []
  for index in vertices {
    reachableIndices[index] = findAllReachableIndices(for: index)
  }
  return reachableIndices
}

func findAllReachableIndices(for startIndex: Int) -> Set<Int> {
  visited: Set<Int> = []
  stack: [Int] = [startIndex]
  while fromIndex = stack.popFirst() {
    visited.insert(fromIndex)

    for toIndex in adjacencyList[fromIndex] {
      if !visited.contains(toIndex) {
        stack.append(toIndex)
      }
    }
  }

  return visited
}

func findCycles(from index: Int, reachableIndices: ref [Set<Int>]) -> [Cycle] {
  result: [Cycle] = []
  dfs(startIndex: index, currentIndex: index, visitedIndices: [], reachableIndices: &reachableIndices, result: &result)

  return result
}

func dfs(startIndex: Int,
         currentIndex: Int,
         visitedIndices: Set<Int>,
         reachableIndices: ref [Set<Int>],
         result: ref [Cycle]) {
  if currentIndex == startIndex && !visitedIndices.isEmpty {
    result.append(cycle)
    return
  }

  if visitedIndices.contains(currentIndex) {
    return
  }

  if !reachableIndices[currentIndex].contains(startIndex) {
    return
  }

  visitedIndices += [currentIndex]

  for toIndex in adjacencyList[currentIndex] {
    dfs(startIndex: startIndex, currentIndex: toIndex, visitedIndices: visitedIndices, result: &result)
  }
}


, , , 5 — . — 15 , 6-7 . , , , — .



6. ?



, , — - . , , - .

, , , .

— " , , , ?". . 5 .



, 250, , , :



func findAllCycles() -> [Cycle] {
  reachableIndices: [Set<Int>] = findAllReachableIndices()
  result: [Cycle] = []
  for index in vertices {
    result += findCycles(from: index, reachableIndices: &reachableIndices)
  }

  return result
}

func findAllReachableIndices() -> [Set<Int>] {
  reachableIndices: [Set<Int>] = []
  for index in vertices {
    reachableIndices[index] = findAllReachableIndices(for: index)
  }
  return reachableIndices
}

func findAllReachableIndices(for startIndex: Int) -> Set<Int> {
  visited: Set<Int> = []
  stack: [Int] = [startIndex]
  while fromIndex = stack.popFirst() {
    visited.insert(fromIndex)

    for toIndex in adjacencyList[fromIndex] {
      if !visited.contains(toIndex) {
        stack.append(toIndex)
      }
    }
  }

  return visited
}

func findCycles(from index: Int, reachableIndices: ref [Set<Int>]) -> [Cycle] {
  result: [Cycle] = []
  dfs(startIndex: index, currentIndex: index, visitedIndices: [], reachableIndices: &reachableIndices, result: &result)

  return result
}

func dfs(startIndex: Int,
         currentIndex: Int,
         visitedIndices: Set<Int>,
         reachableIndices: ref [Set<Int>],
         result: ref [Cycle]) {
  if currentIndex == startIndex && !visitedIndices.isEmpty {
    result.append(cycle)
    return
  }

  if visitedIndices.contains(currentIndex) {
    return
  }

  if currentIndex < startIndex || !reachableIndices[currentIndex].contains(startIndex) {
    return
  }

  visitedIndices += [currentIndex]

  for toIndex in adjacencyList[currentIndex] {
    dfs(startIndex: startIndex, currentIndex: toIndex, visitedIndices: visitedIndices, result: &result)
  }
}


: if currentIndex < startIndex.



, run, , — … 6 ? , … .



, , , . — , A, , A . A, .



, , /.

— , . 5-10% .



6.



5-6 , , ! . , Swift , .

, , 6 … , . — " ? ...". — . — , .



3 , , . , Apple , (, , ). Swift popLast(), . .



( Swift)
var visited: Set<Int> = []
var stack: [Int] = [startVertexIndex]
while let fromIndex = stack.first {
  stack.removeFirst()

  visited.insert(fromIndex)
  for toIndex in graph.adjacencyList[fromIndex].flatMap({ $0.toIndices }) {
    if !visited.contains(toIndex) {
      stack.append(toIndex)
    }
  }
}

return visited


c ( Swift)
var visited: Set<Int> = []
var stack: [Int] = [startVertexIndex]
while let fromIndex = stack.popLast() {
  visited.insert(fromIndex)
  for toIndex in graph.adjacencyList[fromIndex].flatMap({ $0.toIndices }) {
    if !visited.contains(toIndex) {
      stack.insert(toIndex, at: 0)
    }
  }
}

return visited


, , — , Swift 5-10%.





? — 95 , 2.5-3 , .



, , — .

, , 15 , .



— , , , .



Swift .





, , , .



, "" -. , - :



  • graphvis — .
  • — , , , .
  • , .


P.S. 5 , . , 1.5 — 4.5 . .



P.P.S. , . , , //.



UPD: . dot , .

.



UPD: banshchikov Donald B. Johnson. swift.

, .

:



_
(debug) 2.4-2.5 36.2-44.5
() 0.25-0.33 32.1-36.4
6920* 5736
* 5736 5736


時間は、サイクルの検索についてのみ測定されました。サードパーティのライブラリが入力データを便利なライブラリに変換しますが、このような変換には0.1秒もかかりません。そしてご覧のとおり、時間は桁違いに(そしてリリースでは2つ)異なります。そして、そのような大きな違いは、最適でない実装に起因するものではありません。



  • 私が書いたように、私のライブラリでは、エッジは頂点間の遷移ではありません。このような情報はサードパーティの実装に渡すことができないため、見つかったループの数は一致しません。このため、結果が一致することを確認するために、エッジを除く、頂点に沿ったすべての一意のサイクルの結果が検索されます。



All Articles