有向グラフ内のすべてのサイクルを検出するための最も効率的なアルゴリズムは何ですか?
実行する必要があるジョブのスケジュールを表す有向グラフがあります。ジョブはノードであり、依存関係はエッジです。このグラフ内の循環の依存関係につながるエラーのケースを検出する必要があります。
有向グラフ内のすべてのサイクルを検出するための最も効率的なアルゴリズムは何ですか?
実行する必要があるジョブのスケジュールを表す有向グラフがあります。ジョブはノードであり、依存関係はエッジです。このグラフ内の循環の依存関係につながるエラーのケースを検出する必要があります。
回答:
Tarjanの強連結コンポーネントアルゴリズムにはO(|E| + |V|)
時間の複雑さをいます。
他のアルゴリズムについては、ウィキペディアの強く接続されたコンポーネントを参照してください。
O(|E| + |V|)
。白(一度も訪れたことがない)、灰色(現在のノードは訪れたが到達可能なすべてのノードはまだ訪れていない)、および黒(すべての到達可能なノードは現在のノードと一緒に訪れた)を使用したカラーコーディング、灰色のノードが別の灰色のノードを見つけた場合veサイクル。[Cormenのアルゴリズムブックに掲載されているものはかなり多い]。「タージャンのアルゴリズム」がそのようなDFSよりも利点があるかどうか疑問に思います!!
これがジョブのスケジュールであることを考えると、ある時点でソートすることになると思います、それらを提案された実行順序する。
その場合、トポロジーソートの実装は、いずれにせよサイクルを検出する可能性があります。UNIXはtsort
確かにそうです。したがって、個別のステップではなく、tsortingと同時にサイクルを検出する方が効率的であると思います。
したがって、「ループを最も効率的に検出する方法」ではなく、「どのようにして最も効率的にtsortするか」という疑問が生じる可能性があります。答えはおそらく「ライブラリを使用する」でしょうが、次のウィキペディアの記事は失敗しています:
あるアルゴリズムの疑似コードと、Tarjanによる別のアルゴリズムの簡単な説明があります。どちらもO(|V| + |E|)
時間が複雑です。
それを行う最も簡単な方法は、グラフの深さ優先トラバーサル(DFT)を実行することですです。
グラフにn
頂点がある場合、これはO(n)
時間複雑性アルゴリズムです。おそらく各頂点からDFTを実行する必要があるため、全体の複雑度は次のようになります。O(n^2)
ます。
現在の深度の最初のトラバーサルですべての頂点を含むスタックを維持する必要があります。その最初の要素はルートノードです。DFT中に既にスタックにある要素に遭遇した場合、サイクルがあります。
O(n)
スタックをチェックして、すでに訪問済みのノードが含まれているかどうかを確認するよう提案しているのに、時間の複雑さはなぜですか?スタックO(n)
をスキャンすると、新しいノードごとにスタックをスキャンする必要があるため、実行時間が長くなります。O(n)
訪問したノードにマークを付けると達成できます
コーメンらの補題22.11 、Introduction to Algorithms(CLRS)によれば、
有向グラフGは、Gの深さ優先探索でバックエッジが生成されない場合にのみ非循環です。
これはいくつかの回答で言及されています。ここでは、CLRSの第22章に基づくコード例も提供します。グラフの例を以下に示します。
深さ優先検索読み取り用のCLRSの疑似コード:
CLRS図22.4の例では、グラフは2つのDFSツリーで構成されています。1つはノードu、v、x、およびyで構成され、もう1つはノードwおよびzで構成されています。各ツリーには1つのバックエッジが含まれています。1つはxからv、もう1つはzからzです。(自己ループ)。
キー実現が戻るときにエッジがに、遭遇されていることであるDFS-VISIT
隣人を反復処理しながら、機能v
のu
、ノードがで遭遇しましたGRAY
色。
次のPythonコードは、CLRSの疑似コードに、if
サイクルを検出する句を追加したものです。
import collections
class Graph(object):
def __init__(self, edges):
self.edges = edges
self.adj = Graph._build_adjacency_list(edges)
@staticmethod
def _build_adjacency_list(edges):
adj = collections.defaultdict(list)
for edge in edges:
adj[edge[0]].append(edge[1])
return adj
def dfs(G):
discovered = set()
finished = set()
for u in G.adj:
if u not in discovered and u not in finished:
discovered, finished = dfs_visit(G, u, discovered, finished)
def dfs_visit(G, u, discovered, finished):
discovered.add(u)
for v in G.adj[u]:
# Detect cycles
if v in discovered:
print(f"Cycle detected: found a back edge from {u} to {v}.")
# Recurse into DFS tree
if v not in finished:
dfs_visit(G, v, discovered, finished)
discovered.remove(u)
finished.add(u)
return discovered, finished
if __name__ == "__main__":
G = Graph([
('u', 'v'),
('u', 'x'),
('v', 'y'),
('w', 'y'),
('w', 'z'),
('x', 'v'),
('y', 'x'),
('z', 'z')])
dfs(G)
この例では、 time
、サイクルの検出のみに関心があるため、CLRSの疑似コードはキャプチャされない。エッジのリストからグラフの隣接リスト表現を構築するためのボイラープレートコードもあります。
このスクリプトを実行すると、次の出力が出力されます。
Cycle detected: found a back edge from x to v.
Cycle detected: found a back edge from z to z.
これらは、正確にCLRS図22.4の例のバックエッジです。
DFSから始めます。DFS 中にバックエッジが発見された場合にのみ、サイクルが存在します。これはホワイトパス定理の結果として証明されました。
私の意見では、有向グラフでサイクルを検出するための最も理解できるアルゴリズムは、グラフの色付けアルゴリズムです。
基本的に、グラフの色付けアルゴリズムは、DFS方式でグラフをウォークします(深さ優先検索、つまり、別のパスを探索する前にパスを完全に探索します)。バックエッジが見つかると、グラフにループが含まれているとマークされます。
グラフの色付けアルゴリズムの詳細については、次の記事を参照してください:http : //www.geeksforgeeks.org/detect-cycle-direct-graph-using-colors/
また、私はJavaScript https://github.com/dexcodeinc/graph_algorithm.js/blob/master/graph_algorithm.jsでグラフの色付けの実装を提供します
「訪問済み」プロパティをノードに追加できない場合は、セット(またはマップ)を使用し、訪問済みのすべてのノードをセットに追加します。一意のキーまたはオブジェクトのアドレスを「キー」として使用します。
これは、ユーザーが問題を修正する必要があるときに役立つ循環依存関係の「ルート」ノードに関する情報も提供します。
別の解決策は、実行する次の依存関係を見つけようとすることです。そのためには、現在の場所と次に何をする必要があるかを思い出せるスタックが必要です。実行する前に、依存関係がこのスタックに既に存在するかどうかを確認してください。もしそうなら、あなたはサイクルを見つけました。
これはO(N * M)の複雑さを持っているように見えるかもしれませんが、スタックの深さは非常に制限されているため(Nは小さい)、Mは依存関係ごとに小さくなり、「実行された」プラスとして確認できます。葉を見つけたら検索を停止できます(したがって、すべてのノードをチェックする必要はありません -> Mも小さくなります)。
MetaMakeでは、リストのリストとしてグラフを作成し、実行時にすべてのノードを削除して、自然に検索ボリュームを削減しました。実際に独立したチェックを実行する必要はありませんでした。すべて通常の実行中に自動的に行われました。
「テストのみ」モードが必要な場合は、実際のジョブの実行を無効にする「ドライラン」フラグを追加するだけです。
多項式時間の有向グラフですべてのサイクルを見つけることができるアルゴリズムはありません。有向グラフにn個のノードがあり、ノードのすべてのペアが相互に接続している、つまり完全なグラフがあると仮定します。したがって、これらのnノードの空でないサブセットはサイクルを示し、そのようなサブセットは2 ^ n-1個存在します。したがって、多項式時間アルゴリズムは存在しません。したがって、グラフの有向サイクル数を示すことができる効率的な(愚かではない)アルゴリズムがあるとします。まず、強い連結成分を見つけ、次にこれらの連結成分にアルゴリズムを適用します。サイクルはコンポーネント内にのみ存在し、コンポーネント間には存在しないため。
私が行う方法は、訪問した頂点の数を数えて、トポロジカルソートを行うことです。その数がDAG内の頂点の総数より少ない場合は、サイクルがあります。
/mathpro/16393/finding-a-cycle-of-fixed-length私はこのソリューションが特に4長に最適です:)
また、physウィザードは、O(V ^ 2)を実行する必要があると言っています。私はO(V)/ O(V + E)だけが必要だと思います。グラフが接続されている場合、DFSはすべてのノードを訪問します。グラフにサブグラフが接続されている場合、このサブグラフの頂点でDFSを実行するたびに、接続された頂点が見つかり、DFSの次の実行でこれらを考慮する必要はありません。したがって、各頂点に対して実行される可能性は正しくありません。
DFSが既にアクセスした頂点を指すエッジを見つけた場合、そこにサイクルがあります。
グラフがこのプロパティを満たす場合
|e| > |v| - 1
次に、グラフには少なくともサイクルが含まれます。