最終更新
リンドくん
たなべ先生、DFSとBFSって名前をよく聞くんですけど、何が違うんですか?どっちも探索するアルゴリズムですよね?
たなべ
確かにどちらもグラフやツリーを探索するアルゴリズムなんだけど、探索の仕方が全く違うんだよ。
例えるなら、迷路を探索するときに「とにかく奥まで進む派」と「周りを丁寧に見る派」の違いみたいなものかな。
プログラミングを学んでいると、必ず出会うのがDFS(深さ優先探索)とBFS(幅優先探索)です。
この2つのアルゴリズムは、ゲーム開発やルート探索、AIの実装など、様々な場面で活躍します。
しかし、「どちらも探索するアルゴリズム」という点では同じなのに、なぜ2つも存在するのでしょうか?
それは、それぞれが得意とする問題が異なるからなのです。
本記事では、DFSとBFSの基本的な仕組みから実際のコード例、そして実務での使い分けまで、プログラミング初心者の方でも理解できるよう丁寧に解説していきます。
HackATAは、IT技術を習得する人のために広く開かれたオンラインコミュニティです。 現在、無料コミュニティとして開放していますので、ご気軽に参加してください。
✓ 再立ち上げ
✓ コミュニティの方向性について意見募集中
リンドくん
そもそも「深さ優先」と「幅優先」って、具体的にどう違うんですか?
たなべ
わかりやすく説明すると、DFSは「一本道を突き進む」探索方法で、BFSは「同心円状に広がる」探索方法なんだ。迷路で例えると分かりやすいよ。
DFS(Depth-First Search)は、その名の通り「深さ」を優先する探索アルゴリズムです。スタート地点から始めて、行ける限り奥まで進み、行き止まったら一つ前に戻って別の道を試すという動作を繰り返します。
迷路を探索する場面を想像してみてください。DFSを使うと、まず右の道を選び、その先でまた右を選び...と、とにかく一つの道を突き詰めます。行き止まりになったら、分岐点まで戻って別の道を試します。
DFSの主な特徴は以下の通りです。
一方、BFS(Breadth-First Search)は「幅」を優先する探索アルゴリズムです。スタート地点から同じ距離にあるノードを順番に調べていくという特徴があります。
同じ迷路の例で考えると、BFSではまずスタート地点から1歩で行ける全ての場所を調べます。次に、それらの場所から1歩で行ける全ての場所を調べる...という風に、波紋が広がるように探索していきます。
BFSの主な特徴は以下の通りです。
DFSとBFSの最も大きな違いは、使用するデータ構造にあります。
DFSはスタック(後に入れたものを先に取り出す)を、BFSはキュー(先に入れたものを先に取り出す)を使います。
この違いにより、探索の順序が大きく変わります。DFSは「深く狭く」探索し、BFSは「浅く広く」探索するのです。
どちらが優れているということはなく、解決したい問題によって最適な選択が変わるということを覚えておきましょう。
リンドくん
DFSって実際にはどうやってプログラムで書くんですか?
たなべ
DFSには再帰を使う方法とスタックを使う方法の2つがあるんだ。まずは再帰版から見てみようか。これが一番シンプルで理解しやすいよ。
DFSは以下のような手順で動作します。
この「行けるところまで行って、戻る」という動作が、DFSの本質です。
最もシンプルなDFSの実装は、再帰を使った方法です。以下はグラフを探索するPythonコードの例です。
このコードでは、訪問したノードをvisitedセットに記録しながら、再帰的に隣接ノードを探索していきます。
再帰を使わない方法として、スタックを明示的に使った実装もあります。これは再帰の深さ制限を回避したい場合に有効です。
どちらの実装も基本的な動作は同じですが、再帰版はコードがシンプルで理解しやすく、スタック版は処理の流れが明示的という特徴があります。
DFSの計算量を理解しておくことも重要です。
グラフの全てのノードとエッジを1回ずつ訪問するため、効率的なアルゴリズムと言えます。
リンドくん
BFSはどう実装するんですか?DFSと大きく違いますか?
たなべ
構造は似ているんだけど、使うデータ構造がスタックからキューに変わるだけで、探索の順序が全く変わるんだよ。面白いでしょ?
BFSは以下のような手順で動作します。
この「近い順に訪問する」という動作が、BFSの本質です。
BFSの実装には、Pythonのcollections.dequeを使うのが一般的です。
DFSとの違いは、stack.pop()(後から追加したものを取り出す)がqueue.popleft()(先に追加したものを取り出す)に変わっただけです。しかし、この小さな違いが探索順序を大きく変えるのです。
BFSの最も重要な特性は、最初に目的地に到達したときの経路が必ず最短経路になるということです。この性質を活かした実装例を見てみましょう。
このコードでは、経路そのものをキューに保存することで、ゴールに到達したときに最短経路を取得できるようにしています。
BFSの計算量もDFSと同様です。
ただし、実際のメモリ使用量はDFSより多くなる傾向があります。これは、同じレベルの全ノードをキューに保持する必要があるためです。
リンドくん
2つのアルゴリズムの違いは分かったんですけど、実際にはどう使い分けるんですか?
たなべ
それが一番大事なポイントだね!解きたい問題の性質によって、どちらが適しているか変わってくるんだ。具体的な例を見ながら考えてみよう。
DFSは以下のような問題に適しています。
全ての経路を列挙したい場合
迷路の全ての解を見つけたい、組み合わせを全て試したいといった場面では、DFSが活躍します。
トポロジカルソート
タスクの依存関係を解決する場合など、順序付けが必要な問題にはDFSが使われます。
メモリ使用量を抑えたい場合
グラフが非常に広い(多くの分岐がある)場合、BFSはメモリを大量に消費しますが、DFSは比較的少ないメモリで動作します。
一方、BFSは以下のような問題に適しています。
最短経路を見つけたい場合
ゲームのキャラクターの移動経路、ナビゲーションシステムなど、最短距離が重要な場面ではBFSが最適です。
レベル順の処理が必要な場合
木構造のレベル順走査、ネットワークの階層的な処理など、「同じ深さのノードをまとめて処理したい」場合にはBFSが適しています。
最も近い要素を見つけたい場合
SNSの「友達の友達」を探す、最寄りの店舗を見つけるといった、「近さ」が重要な問題ではBFSが活躍します。
具体的な例として、迷路を解く問題を考えてみましょう。
この例では、BFSを使って迷路の最短経路を見つけています。同じ問題をDFSで解くこともできますが、DFSでは最短経路が保証されません。
以下の表を参考に、問題に応じてアルゴリズムを選びましょう。
| 要件 | 推奨アルゴリズム | 理由 |
|---|---|---|
| 最短経路が必要 | BFS | 最初に見つかる経路が最短 |
| 全ての経路を列挙 | DFS | 深く探索して全パターンを試す |
| メモリ使用量を抑えたい | DFS | スタックの深さ分のみ |
| レベル順の処理 | BFS | 同じ深さを一度に処理 |
| 解の存在確認のみ | どちらでも | 好みで選択可能 |
プログラミングの面白いところは、同じ問題でも複数の解法があり、それぞれに長所と短所があるということです。DFSとBFSはその代表例と言えるでしょう。
リンドくん
DFSとBFSの違いがよく分かりました!実際に自分でも実装してみたくなりました。
たなべ
素晴らしい!実際に手を動かして試してみることが一番の学習になるよ。
最初は簡単なグラフから始めて、徐々に複雑な問題に挑戦していこう。アルゴリズムは理解するだけでなく、使いこなせるようになることが大切だからね。
今回は、グラフ探索の基本となるDFSとBFSについて、基礎から実装方法、使い分けまで詳しく解説してきました。
DFS(深さ優先探索)の特徴
BFS(幅優先探索)の特徴
これらのアルゴリズムは、単なる理論ではありません。実際のソフトウェア開発、特にゲーム開発やAI実装において、日常的に使われる技術です。
例えば、RPGゲームでキャラクターが敵を追いかける動き、パズルゲームの自動解法、SNSの友達推薦機能など、様々な場面で活用されています。
アルゴリズムの学習は、最初は難しく感じるかもしれません。しかし、一つ一つ理解して実装できるようになると、プログラミングの世界が大きく広がります。
コンピュータサイエンスの基礎を身につけることは、AI時代のエンジニアにとって大きな武器になるはずです。