DAGにエッジを追加するとサイクルが発生するかどうかを確認する


7

初めに:これはプログラミングコンテストの問題ですが、進行中の問題ではありません。残念ながら、公開されていないため、このタスクへのリンクは提供できません。それは、2011年にワルシャワコンピュータサイエンススクールが主催したポーランドのローカルプログラミングコンテストの1つからのものでした。

エッジのない頂点のあるグラフと、有向エッジのリストがあります。上の番目の第番目のエッジグラフに追加されています。グラフに1秒後にサイクルが発生することを知りたい。V (1V5105)E (1E5105)ii

最も明白な解決策は、各エッジを追加した後にDFSを実行することですが、時間かかります。別の解決策は、トポロジー的にソートされたグラフを維持し、エッジを追加するときに、トポロジーの順序を乱さないように配置することです。これには最大で時間かかります。私はGoogleでいくつかの調査を行いましたが、これが最速のオンラインアルゴリズムのようです。O((V+E)2)O(V2)

事前にすべてのエッジを知っているので、オフラインアルゴリズムを使用できます。私が考えることができる最速のオフラインアルゴリズムは、バイナリ検索です。番目の2番目以降のグラフにサイクルが含まれている場合、明らかに他の2番目のサイクルがあります。したがって、バイナリ検索を実行してDFS時間を実行することで最小のを見つけることができます。それぞれが時間を費やすため、このソリューションの全体の複雑さは。klkkO(logE)O(V+E)O((V+E)logE)

かなり高速ですが、もっと高速なオフラインアルゴリズムはあるのでしょうか。私が考えることができる1つのことは、このエッジがグラフに追加された時間に等しい各エッジの重みを与えることです。次に、このタスクは、最小の最大エッジウェイトを持つサイクルを見つけることと同じです。それがどこにつながっているかはわかりません。


2
Google Scholarで「オンラインサイクル検出」を検索することもできますが、これらのアルゴリズムはバイナリ検索アプローチよりも遅くなると思います。
DW

2
おそらく、葉の反復削除(カーンのアルゴリズム)アルゴリズムを変更して、最大エッジを削除できます。つまり、(1)リーフノードが見つからなくなるまで削除し続け、次に(2)番号が最も大きいエッジを削除して、空になるまで繰り返します。最後の(2)は目的のエッジです。
KWillets 2016年

2
はい、前のコメントを回答にエスカレートしました。カーンを調べて、追加のステップが意味をなすかどうかを確認するのにしばらく時間がかかりました。
KWillets

回答:


2

KWilletsのアルゴリズム

KWillets が最高のアルゴリズムを持っているようです。基本的に、次の2つのステップを交互に繰り返します。

  1. 各ソース(発信エッジのない頂点)について、ソースとそこから出ているすべてのエッジを削除します。ソースがなくなるまで繰り返します。

  2. 最も大きい番号のエッジ、つまり、(残りのすべてのエッジから)最も遅い時刻にあるエッジを削除します。手順1に戻ります。

グラフが空の場合は、最後の手順1と手順2を元に戻します。サイクルを持つ最初のグラフがあります。つまり、グラフが空になる前に、最後の手順2で削除された最後のエッジを取得します。そのエッジとそれ以前のすべてのエッジを含むグラフは、サイクルを含む最初のエッジです。

エッジのリストは時間の増加によってすでにソートされているため、これは時間で実装できます。これらのノードとグラフ内の対応するエッジとの間のポインタを使用して、二重リンクリストでエッジのリストを維持します。手順1では、ノードまたはエッジごとに時間を削除する必要があります。すべてのソース頂点の個別のワークリストを保持し、エッジを削除するたびに、新しいソース頂点が作成されたかどうかを確認します。O(V+E)O(1)

二分探索

バイナリ検索メソッドにいくつかの小さな最適化を行うことは可能ですが、漸近的な実行時間は改善されません。漸近的な実行時間はです。O((V+E)logE)

時間でグラフを分析し、サイクルが含まれていることを発見したとします。グラフを強連結成分に分解します。これで、すべての分離されたノード(つまり、サイズが1のSCCにあるノード)を完全に削除できます。サイクルの一部にすることはできません。ここからバイナリ検索を続行できます。k

また、強連結成分に分解するときは、強連結成分ごとに次のことを行います。SCC内のノードの数を数えます。たとえば、とします。時間の経過とともにSCC内のエッジを並べ替えます。時のルック彼らの目には、時間と言う。次に、その強連結成分内にサイクルがある最初の時間は、以降の時間でなければなりません。強く接続されたコンポーネントごとにこれを行い、それらの時間の中で最も早いものを取ります。次に、最適化として、バイナリ検索時よりも早い時期を考慮する必要はありません。n0n0j0j0

ただし、これらの2つの最適化は、せいぜい、小さな定数係数以上のスピードアップを提供しないでしょう。


これは良いことです-詳細をまとめるのに少し時間が必要でしたが、ATMが忙しいので、セカンドオピニオンに感謝します:)
KWillets

@KWillets、あなたのアルゴリズムはとても賢いです!いい物。
DW

6

カーンのアルゴリズムはトポロジカルソートで知られていますが、強連結成分を見つけるためにも使用できます。実際のトポロジカルソートが不要なサイクルチェックに使用します。このバリアントでは、ノードは、出力に追加されるのではなく、アクセスされるときに破棄され、エラー終了は少なくとも1サイクルを示します。

これは、ルートノードまたはリーフノード(次数が0または次数が0のノード)を一度に1つずつグラフから削除し、そのタイプのノードが見つからなくなるまで機能します。このプロセスはサイクルをカットできないため(サイクルのすべてのノードのインディグリーとアウトディグリー> 0)、少なくとも1つのサイクルが存在する場合、空でないグラフで終了します。

そのような各終了ポイントで残りの最大エッジeをカットすると、E \ eにまだサイクルが含まれているかどうかを確認するためにプロセスを続行できます。最後のこのようなエッジカットkは、E [1 ... k]を循環させる最初のkです。

カーンのアルゴリズムは、削除するノードの作業キューを使用して、このリーフカットプロセスを直線的に実行します。グラフ全体の葉(非循環の場合は少なくとも1つは存在している必要があります)で初期化され、それぞれが削除されると、親ノードがチェックされ、葉になるかどうかが確認されます。それらは作業キューに追加されます。各削除の近傍をチェックすることにより、FIFOキューを超えて構造にインデックスを付ける必要がなく、グラフ全体で線形に機能します。

最大エッジをカットするときと同じネイバーリーフチェックを実行して、作成されたリーフを作業キューに追加できます。作成されていない場合は、カットを続行します。

max-cuttingプロセスを線形にするために、エッジ配列の降順スキャンを介して、削除されていない最大のエッジを見つける必要がありますが、各スキャンは、前の処理が終了したところから始まり、アルゴリズムは、配列全体なので、その部分は合計でO(E)です。全体として、この方法はO(V + E)であり、カーンの方法と同じであると思います。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.