web-dev-qa-db-ja.com

有向グラフにエッジを追加するとサイクルが発生するかどうかを検出するにはどうすればよいですか?

私は グラフ待ち に出会いました。そして、有向グラフにエッジを追加することでサイクルが発生するかどうかを検出するための効率的なアルゴリズムはありますか?

問題のグラフは変更可能です(ノードやエッジを追加または削除できます)。そして、問題のあるサイクルを実際に知ることには関心がなく、それがあることを知るだけで十分です(問題のあるエッジの追加を防ぐため)。

もちろん、アルゴリズムを使用して、強く接続されたコンポーネント(Tarjanなど)を計算し、新しいグラフが非循環かどうかを確認することは可能ですが、Edgeが追加されるたびに再度実行することは非常に非効率的です。

32
Petr Pudlák

私があなたの質問を正しく理解した場合、新しいエッジ(u、v)は、以前にvからuへのパスがなかった場合(つまり、(u、v)がサイクルを作成しない場合)にのみ挿入されます。したがって、グラフは常にDAG(有向非循環グラフ)です。 Tarjanのアルゴリズムを使用して強く接続されたコンポーネント( http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm )を検出すると、この場合はやり過ぎに聞こえます。 (u、v)を挿入する前に確認する必要があるのは、vからuへの有向パスがあるかどうかだけです。これは、単純なBFS/DFSで実行できます。

したがって、最も簡単な方法は次のとおりです(n = | V |、m = | E |):

  • Inserting(u、v):vからu(BFS/DFS)へのパスがあるかどうかを確認します。時間の複雑さ:O(m)
  • エッジを削除する:単にグラフからエッジを削除します。時間の複雑さ:O(1)

(u、v)の挿入にはO(m)最悪の場合時間がかかりますが、状況によってはかなり高速です。vから始まるBFS/DFSを実行してuが到達可能、vから到達可能な頂点のみにアクセスします。あなたの設定では、グラフがかなり疎であり、別の頂点から到達可能な頂点の数はそれほど多くないと思います。

ただし、理論上の実行時間を改善したい場合は、いくつかのヒントを示します(主にこれは非常に簡単ではないことを示しています)。 O(1)時間でvからuへの有向パスが存在するかどうかをテストすることを目的としていると仮定します。このコンテキストのキーワードは、推移的閉包ですDAG(つまり、DAGにuからvへの有向パスが存在する場合に限り、エッジ(u、v)を含むグラフ)です。残念ながら、動的な設定はそれほど単純ではないようです。この問題を検討している論文がいくつかあり、私が見つけたすべての論文はSTOCまたはFOCSの論文であり、very関与しました。私が見つけた最新の(そして最速の)結果は、Sankowskiによる論文Dynamic Transitive Closure via Dynamic Matrix Inverseにあります( http ://dl.acm.org/citation.cfm?id = 1033207 )。

これらの動的推移的閉包アルゴリズムの1つを理解してもよい(またはそれを実装したい)場合でも、次の理由により速度が向上しません。これらのアルゴリズムは、多数の接続クエリ(O(1)時間で実行可能)があり、グラフにわずかな変更のみが加えられる)の状況向けに設計されています。その場合の目標はこれらの変更を推移的閉包の再計算よりも安くします。ただし、この更新は接続の単一のチェックよりも遅くなります。したがって、すべての接続クエリで更新を行う必要がある場合は、上記の簡単な方法を使用することをお勧めします。

それでは、なぜ推移的なクロージャを維持するこのアプローチが、ニーズに合わない場合に言及するのですか?まあ、それはO(1)クエリ時間のみを消費するアルゴリズムを検索しても、おそらくBFS/DFSを使用する単純なものよりも速くソリューションに導けないことを示しています。クエリ時間はO(m)よりも速いがO(1)よりも悪いが、更新もO(m)よりも速い。これは非常に興味深い問題ですが、私は非常に野心的な目標が好きです(多分、それを達成するためにあまり時間をかけないでください。)。

34
Thomas

Markが示唆したように、接続されたノードを格納するデータ構造を使用することが可能です。ブール行列_|V|x|V|_を使用するのが最適です。値は、フロイドワーシャルアルゴリズムで初期化できます。それはO(|V|^3)で行われます。

T(i)を頂点iへのパスを持つ頂点のセットとし、F(j)頂点jからのパスが存在する頂点のセットとします。最初は、i番目の行のtrueであり、2番目は、j番目の列のtrueです。

エッジの追加_(i,j)_は簡単な操作です。 ijが以前に接続されていなかった場合は、T(i)の各aF(j)の各bがマトリックス要素_(a,b)_をtrueに設定します。しかし、操作は安くはありません。最悪の場合はO(|V|^2)です。これは有向線の場合であり、端から開始の頂点にエッジを追加すると、すべての頂点が他のすべての頂点に接続されます。

エッジの削除_(i,j)_はそれほど単純ではありませんが、最悪の場合、より高価な操作ではありません:-) Edgeの削除後にiからjへのパスがある場合、何も変更されません。それはO(|V|^2)より小さいダイクストラでチェックされます。もう接続されていない頂点は_(a,b)_です:

  • a in T(i)-i-T(j)
  • b in F(j) + j

Edge _(i,j)_を削除するとT(j)のみが変更されるため、再計算する必要があります。これは、頂点jから反対のエッジ方向に移動することにより、あらゆる種類のグラフトラバース(BFS、DFS)によって行われます。それはO(|V|^2)より少ない時間で行われます。行列要素の設定が最悪の場合もO(|V|^2)であるため、この操作はEdgeを追加する場合と同じ最悪の場合の複雑さを持っています。

5
Ante

グラフが指示されている場合は、新しいEdgeを開始するノードの親ノード(ルートに到達するまで上に移動)を確認するだけで済みます。親ノードの1つがエッジの終わりと等しい場合、エッジを追加するとサイクルが作成されます。

0
user3072850

以前のすべてのジョブがトポロジー的にソートされている場合。次に、並べ替えの妨げになり、修正できないエッジを追加すると、サイクルが発生します。

https://stackoverflow.com/a/261621/83185

したがって、ノードのソートされたリストがある場合:

1, 2, 3, ..., x, ..., z, ...
Such that each node is waiting for nodes to its left.

X-> zからEdgeを追加したいとします。まあそれは種類をブレーキングするように見えます。したがって、xのノードをz + 1の位置に移動できます。これにより、ノード(x、z]にxのノードへのエッジがない場合、並べ替えが修正されます。

0
Jacob