主に、BFSではなくグラフのサイクルを見つけるためにDFSが使用されます。何か理由は?どちらも、ツリー/グラフを走査するときにノードがすでにアクセスされているかどうかを確認できます。
深さ優先検索は、幅広優先検索よりもメモリ効率が良いため、より早くバックトラックできます。コールスタックを使用する場合も実装が簡単ですが、これはスタックをオーバーフローさせない最長パスに依存します。
また、グラフが 有向 の場合、ノードにアクセスしたかどうかだけでなく、そこに到達した方法も覚えておく必要があります。そうでなければ、サイクルを見つけたと思うかもしれませんが、実際には、A-> Bの2つの別々のパスしかありませんが、B-> Aのパスがあるわけではありません。例えば、 -
0
からBFSを開始すると、サイクルが存在することを検出しますが、実際にはサイクルはありません。
深さ優先検索を使用すると、ノードの下降時にアクセス済みとしてマークし、バックトラック時にノードのマークを解除できます。このアルゴリズムのパフォーマンスの改善に関するコメントを参照してください。
有向グラフでサイクルを検出するための最良のアルゴリズム については、 Tarjanのアルゴリズム を見ることができます。
グラフが無向の場合(有向グラフでサイクルを報告するBFSを使用した効率的なアルゴリズムを見せてください!)、各「クロスエッジ」がサイクルを定義する場合、BFSは合理的です。クロスエッジが{v1, v2}
、およびそれらのノードを含むルート(BFSツリー内)はr
であり、サイクルはr ~ v1 - v2 ~ r
(~
はパス、-
単一のEdge)。これは、DFSとほぼ同じくらい簡単に報告できます。
BFSを使用する唯一の理由は、(無向)グラフに長いパスと小さなパスカバー(つまり、深くて狭い)があることを知っている場合です。その場合、BFSのキューに必要なメモリは、DFSのスタックよりも比例して少なくなります(どちらももちろん線形です)。
他のすべての場合、DFSが明らかに勝者です。有向グラフと無向グラフの両方で機能し、サイクルを報告するのは簡単です-祖先から子孫へのパスに戻るエッジを連結するだけです、そしてあなたはサイクルを取得します。全体として、この問題に対するBFSよりもはるかに優れた実用的です。
BFSは、サイクルを見つける際に有向グラフでは機能しません。 A-> BおよびA-> C-> BをグラフのAからBへのパスと考えてください。 BFSは、Bが訪問したパスの1つに沿って進んだ後、それを通知します。次のパスを移動し続けると、マークされたノードBが再び見つかったと言うため、サイクルがあります。明らかにここにはサイクルはありません。
ツリー内のランダムな場所にサイクルを配置すると、DFSはツリーの約半分が覆われたときにサイクルにヒットする傾向があり、その半分の時間は既にサイクルが進む場所を通過しており、半分の時間はそうではありません(ツリーの残りの半分で平均して検出されます)、平均でツリーの約0.5 * 0.5 + 0.5 * 0.75 = 0.625と評価されます。
ツリー内のランダムな場所にサイクルを配置すると、BFSはその深さでツリーのレイヤーを評価したときにのみサイクルにヒットする傾向があります。したがって、通常、バランスバイナリツリーの葉を評価する必要があり、一般に、より多くのツリーを評価することになります。特に、2つのリンクのうち少なくとも1つがツリーの葉に表示される時間の3/4であり、これらの場合、ツリーの平均3/4(リンクが1つある場合)または7 /ツリーが8つ(2つある場合)なので、すでに1/2 * 3/4 + 1/4 * 7/8 =(7 + 12)/ 32 = 21/32 =を検索することを期待しています。リーフノードから離れてサイクルが追加されたツリーを検索するコストを追加することなく、ツリーの0.656 ...
さらに、DFSはBFSよりも実装が簡単です。したがって、サイクルについて何か知っている場合を除き、これを使用します(たとえば、サイクルは検索元のルートの近くにある可能性が高く、この時点でBFSが有利になります)。
グラフが周期的であることを証明するには、1つのサイクル(直接または間接的にエッジ自体を指す)があることを証明する必要があります。
DFSでは、一度に1つの頂点を取得し、サイクルがあるかどうかを確認します。サイクルが見つかるとすぐに、他の頂点のチェックを省略できます。
BFSでは、多くの頂点エッジを同時に追跡する必要があります。多くの場合、最後に、サイクルがあるかどうかを確認します。グラフのサイズが大きくなると、BFSはDFSと比較してより多くのスペース、計算、および時間を必要とします。
再帰的または反復的な実装について話しているかどうかによって異なります。
再帰的DFSはすべてのノードを2回訪問します。反復BFSは、すべてのノードを1回訪問します。
サイクルを検出する場合は、ノードで「開始」するときとノードで「終了する」ときの両方で、隣接関係を追加する前後にノードを調査する必要があります。
これには反復BFSでより多くの作業が必要なので、ほとんどの人は再帰的DFSを選択します。
たとえば、std :: stackを使用したIterative-DFSの単純な実装には、Iterative-BFSと同じ問題があることに注意してください。その場合、ダミー要素をスタックに配置して、ノードでの作業を「終了」するタイミングを追跡する必要があります。
Itoative-DFSがノードを「終了」するタイミングを決定するために追加の作業が必要になる方法の詳細については、この回答を参照してください(TopoSortのコンテキストで回答):
うまくいけば、ノードの処理を「終了」するタイミングを判断する必要がある問題に、Recursive-DFSを好む理由が説明されます。