グラフでサイクルを見つけるためにBFSではなくDFSを使用する理由


85

主にDFSは、BFSではなくグラフのサイクルを見つけるために使用されます。何か理由はありますか?どちらも、ツリー/グラフをトラバースしているときにノードがすでにアクセスされているかどうかを確認できます。


5
有向グラフでは、DFSのみを使用してサイクルを検出できます。ただし、無向グラフでは両方を使用できます。
ヘンガメ2015

回答:


73

深さ優先探索は、より早くバックトラックできるため、幅優先探索よりもメモリ効率が高くなります。コールスタックを使用すると実装も簡単になりますが、これはスタックをオーバーフローしない最長のパスに依存します。

また、グラフが指示されている場合は、ノードにアクセスしたかどうかだけでなく、どのようにしてそこに到達したかを覚えておく必要があります。そうでなければ、あなたはあなたがサイクルを見つけたと思うかもしれませんが、実際にはあなたが持っているのは2つの別々のパスA-> Bだけですが、それはパスB-> Aがあるという意味ではありません。例えば、

から開始してBFSを実行0すると、サイクルが存在することを検出しますが、実際にはサイクルはありません。

深さ優先探索を使用すると、下降時にノードを訪問済みとしてマークし、バックトラック時にノードのマークを解除できます。このアルゴリズムのパフォーマンスの向上については、コメントを参照してください。

有向グラフでサイクルを検出するための最良のアルゴリズムについては、Tarjanのアルゴリズムを参照してください。


3
(バックトラックが早くなるのでメモリ効率が高く、オープンリストを明示的に維持する代わりにスタックにオープンリストの保存を任せることができるので実装が簡単です。)
アンバー

3
IMO、末尾再帰に頼ることができる場合にのみ簡単です。
ハンクゲイ2010年

2
「バックトラックするときにそれらのマークを外す」-あなた自身の危険で!これは簡単にO(n ^ 2)の動作につながる可能性があります。特に、このようなDFSはクロスエッジを「ツリー」エッジと誤解します(「ツリー」エッジは実際にはツリーを形成しないため、誤った名称にもなります)
Dimitris Andreou

1
@Dimitris Andreo:パフォーマンスを向上させるために、2つではなく3つの訪問済み状態を使用できます。有向グラフでは、「このノードは以前に見たことがあります」と「このノードはループの一部です」の間に違いがあります。無向グラフでは、それらは同等です。
マークバイアーズ2010年

正確には、(アルゴリズムを線形にするために)必ず3番目の状態が必要なので、その部分を修正することを検討する必要があります。
Dimitris Andreou 2010年

28
  1. DFSは実装が簡単です
  2. DFSがサイクルを検出すると、スタックにはサイクルを形成するノードが含まれます。同じことはBFSには当てはまらないため、見つかったサイクルも印刷する場合は、追加の作業を行う必要があります。これにより、DFSがはるかに便利になります。

10

グラフが無向である場合(有向グラフでサイクルを報告するBFSを使用した効率的なアルゴリズムを示すゲストになります!)、BFSは妥当である可能性があります。クロスエッジが{v1, v2}であり、それらのノードを含むルート(BFSツリー内)がrである場合、サイクルはr ~ v1 - v2 ~ r~パス、-単一エッジ)であり、DFSの場合とほぼ同じくらい簡単に報告できます。

BFSを使用する唯一の理由は、(無向の)グラフに長いパスと小さなパスカバー(つまり、深くて狭い)があることがわかっている場合です。その場合、BFSはDFSのスタックよりもキューに必要なメモリが比例して少なくなります(もちろんどちらも線形です)。

他のすべての場合、DFSが明らかに勝者です。有向グラフと無向グラフの両方で機能し、サイクルを報告するのは簡単です。祖先から子孫へのパスにバックエッジを連結するだけで、サイクルが得られます。全体として、この問題については、BFSよりもはるかに優れて実用的です。


4

BFSは、サイクルを見つける際の有向グラフでは機能しません。A-> BおよびA-> C-> Bを、グラフのAからBへのパスと見なします。BFSは、パスの1つに沿って進んだ後、Bが訪問されたと言います。次のパスを移動し続けると、マークされたノードBが再び見つかったと表示されます。したがって、サイクルがあります。明らかにここにはサイクルがありません。


DFSがあなたの例にサイクルが存在しないことを明確に識別する方法を説明できますか?提供された例にサイクルが存在しないことに同意しますが、A-> BからA-> C-> Bに進むと、 Bはすでに訪問されており、その親はCではなくAです。DFSは、すでに訪問された要素の親を、現時点でチェックしている現在のノードと比較することによってサイクルを検出することを読みました。DFSが間違っているか、何?
スマッシャー2017年

ここで示したのは、この特定の実装が機能しないことだけであり、BFSでは不可能ではありません。実際に、より多くの作業とスペースが必要ですが、それ可能です。
2017年

@Prune:ここにあるすべてのスレッド(私は思う)は、bfsがサイクルの検出に機能しないことを証明しようとしています。あなたが反証する方法を知っているなら、あなたは証拠を与えるべきです。努力がもっと大きいと言うだけでは十分ではありません
Aditya Raman

リンクされた投稿にアルゴリズムが記載されているので、ここで概要を繰り返すのは適切ではないと思います。
2017年

リンクされた投稿が見つからなかったため、同じように依頼しました。私はbfs機能についてのあなたの意見に同意し、実装について考えたところです。ヒントをありがとう:)
Aditya Raman

3

なぜこのような古い質問がフィードに表示されたのかわかりませんが、以前の回答はすべて悪いので...

それはのでDFSは、有向グラフにおけるサイクルを見つけるために使用されて動作します

DFSでは、すべての頂点が「訪問」されます。頂点への訪問とは、次のことを意味します。

  1. 頂点が開始されます
  2. その頂点から到達可能なサブグラフにアクセスします。これには、その頂点から到達可能なすべてのトレースされていないエッジのトレース、および到達可能なすべての未訪問の頂点へのアクセスが含まれます。

  3. 頂点が完成しました。

重要な機能は、頂点から到達可能なすべてのエッジが、頂点が終了する前にトレースされることです。これはDFSの機能ですが、BFSではありません。実際、これがDFSの定義です。

この機能により、サイクルの最初の頂点が開始されると、次のことがわかります。

  1. サイクル内のどのエッジもトレースされていません。これは、サイクル内の別の頂点からのみアクセスできるためです。最初に開始する頂点について説明しています。
  2. その頂点から到達可能なすべてのトレースされていないエッジは、終了する前にトレースされます。これには、まだトレースされていないため、サイクル内のすべてのエッジが含まれます。したがって、サイクルがある場合、開始後、終了前に最初の頂点に戻るエッジが見つかります。そして
  3. トレースされるすべてのエッジは、開始されたが未完了のすべての頂点から到達可能であるため、そのような頂点へのエッジを見つけることは常にサイクルを示します。

したがって、サイクルがある場合は、開始されているが未完了の頂点へのエッジを見つけることが保証され(2)、そのようなエッジを見つけた場合は、サイクルがあることが保証されます(3)。

そのため、DFSは有向グラフのサイクルを見つけるために使用されます。

BFSはそのような保証を提供しないため、機能しません。(サブプロシージャとしてBFSまたは同様のものを含む完全に優れたサイクル検出アルゴリズムにもかかわらず)

一方、無向グラフには、頂点のペアの間に2つのパスがある場合、つまりツリーでない場合は常にサイクルがあります。これは、BFSまたはDFSのいずれかで簡単に検出できます。新しい頂点にトレースされたエッジはツリーを形成し、他のエッジはサイクルを示します。


確かに、これはここで最も(おそらく唯一の)関連する答えであり、実際の理由を詳しく説明しています。
プラズマセル

2

ツリーのランダムな場所にサイクルを配置すると、DFSは、ツリーの約半分が覆われたときにサイクルにヒットする傾向があり、サイクルが進む場所をすでに通過した時間の半分と、通過しない時間の半分(そして、ツリーの残りの半分で平均してそれを見つけるでしょう)、それでそれはツリーの平均で約0.5 * 0.5 + 0.5 * 0.75 = 0.625を評価します。

ツリーのランダムな場所にサイクルを配置すると、BFSは、その深さでツリーのレイヤーを評価した場合にのみサイクルにヒットする傾向があります。したがって、通常、バランス二分木の葉を評価する必要があり、その結果、通常、より多くのツリーが評価されます。特に、3/4の時間、2つのリンクの少なくとも1つがツリーの葉に表示されます。そのような場合、ツリーの平均3/4(リンクが1つある場合)または7 /を評価する必要があります。ツリーの8(2つある場合)なので、1/2 * 3/4 + 1/4 * 7/8 =(7 + 12)/ 32 = 21/32 =を検索することを期待できます。リーフノードから離れて追加されたサイクルでツリーを検索するコストを追加することなく、ツリーの0.656...。

さらに、DFSはBFSよりも実装が簡単です。したがって、サイクルについて何かを知らない限り、これを使用します(たとえば、サイクルは検索元のルートの近くにある可能性が高く、その時点でBFSが有利になります)。


そこにはたくさんの魔法数があります。「DFSの方が速い」という議論には同意しません。これは完全に入力に依存し、この場合、他の入力よりも一般的な入力はありません。
IVlad 2010年

@ Vlad-数字は魔法ではありません。それらは手段であり、そのように述べられており、私が述べた仮定を考えると、計算するのはほとんど簡単です。平均で近似することが悪い近似である場合、それは正当な批判になります。(そして、構造について仮定を立てることができれば、答えは変わる可能性があると明確に述べました。)
Rex Kerr 2010年

数字は何の意味もないので魔法のようです。DFSの方が優れているケースを取り上げ、それらの結果を一般的なケースに外挿しました。あなたの発言は根拠がありません:「DFSはツリーの約半分をカバーするとサイクルにぶつかる傾向があります」:それを証明してください。言うまでもなく、ツリー内のサイクルについて話すことはできません。定義上、ツリーにはサイクルがありません。私はあなたのポイントが何であるかわかりません。DFSは行き止まりに達するまで一方向に進むため、平均してどれだけのグラフ(ツリーではない)を探索するかを知る方法はありません。あなたは何も証明しないランダムなケースを選んだだけです。
IVlad 2010年

@ Vlad-すべての非循環完全接続無向グラフは(ルート化されていない無向)ツリーです。私は「1つの偽のリンクを除いてツリーになるグラフ」を意味しました。おそらく、これはアルゴリズムの主なアプリケーションではありません。ツリーではないリンクが非常に多い絡み合ったグラフでサイクルを見つけたい場合があります。しかし、それがツリーのようであり、すべてのグラフで平均化されている場合、どのノードも同様にスプリアスリンクのソースである可能性が高く、リンクがヒットしたときに予想されるツリーカバレッジが50%になります。したがって、この例は代表的なものではなかった可能性があることを認めます。しかし、数学は取るに足らないものでなければなりません。
レックスカー2010年

1

グラフが周期的であることを証明するには、グラフに1つの周期があることを証明する必要があります(エッジが直接または間接的にそれ自体を指している)。

DFSでは、一度に1つの頂点を取得し、それがサイクルを持っているかどうかを確認します。サイクルが見つかるとすぐに、他の頂点のチェックを省略できます。

BFSでは、多くの頂点エッジを同時に追跡する必要があります。多くの場合、最後にサイクルがあるかどうかを確認します。グラフのサイズが大きくなると、BFSはDFSと比較して、より多くのスペース、計算、および時間を必要とします。


0

再帰的実装と反復的実装のどちらについて話しているかによって異なります。

再帰的-DFSはすべてのノードに2回アクセスします。反復-BFSはすべてのノードに1回アクセスします。

サイクルを検出する場合は、ノードを「開始」するときと「終了」するときの両方で、隣接ノードを追加する前と後の両方でノードを調査する必要があります。

これにはIterative-BFSでより多くの作業が必要になるため、ほとんどの人はRecursive-DFSを選択します。

たとえば、std :: stackを使用したIterative-DFSの単純な実装には、Iterative-BFSと同じ問題があることに注意してください。その場合、ノードでの作業を「終了」したときに追跡するために、ダミー要素をスタックに配置する必要があります。

Iterative-DFSがノードで「終了」するタイミングを決定するために追加の作業が必要になる方法の詳細については、この回答を参照してください(TopoSortのコンテキストで回答)。

再帰なしのDFSを使用したトポロジカルソート

うまくいけば、それが、ノードの処理を「終了」するタイミングを決定する必要がある問題に対して、人々がRecursive-DFSを好む理由を説明しています。


再帰を使用するか、反復によって再帰を排除するかは問題ではないため、これは完全に間違っています。すべてのノードに1回だけアクセスする再帰的バリアントを実装できるのと同じように、すべてのノードに2回アクセスする反復DFSを実装できます。
プラズマセル

0

あなたは使用する必要があります BFS有向グラフで特定のノードを含む最短のサイクルを見つけたい場合する必要があります。

例えば:ここに画像の説明を入力してください

指定されたノードが2の場合、それが- [2,3,4][2,3,4,5,6,7,8,9]&の一部である3つのサイクルがあります。[2,5,6,7,8,9]ます。最短は[2,3,4]

BFSを使用してこれを実装するには、適切なデータ構造を使用して、訪問したノードの履歴を明示的に維持する必要があります。

ただし、他のすべての目的(たとえば、循環パスを見つける、またはサイクルが存在するかどうかを確認する)の場合DFSは、他の人が言及している理由から、明確な選択です。

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