グラフ ・ O(V + E)

深さ優先探索

行き止まりまで奥へ進み、戻ってから別の隣を試します。

1 歩ずつ動かす

ABCDEFG
いま奥にいる頂点
A
見ている辺
なし
訪れた順
A

いま通っている道 (下が開始の頂点、上がいちばん奥)

  1. A
戻った頂点
  • いちばん奥にいる頂点
  • 訪問済み (右上の数は訪れた順)
  • まだの頂点
  • いま見ている辺

隣が 2 つ以上あるときは、名前の順 (A、B、C…) に見ます。訪問済みにするのは、その頂点へ着いたときです。

1 歩目 / 全 21 歩見た辺 0 本訪問済み 1 個道の長さ 1

開始の頂点 A を訪問済みにして、道に積みます。隣は B、C です。名前の順に見ます。

いま動いている疑似コードの行

  1. 1visit(s) を呼ぶ (s は開始の頂点)
  2. 2visit(u) は、まず u を訪問済みにする
  3. 3u の隣 v を、名前の順に 1 つずつ見る
  4. 4まだの v は、visit(v) を呼んで奥へ進む
  5. 5訪問済みの v は、何もしない
  6. 6隣を全部見終えたら、呼び出し元へ戻る

見本を変える

予想してみる

いまは 1 歩目です。
次に訪問済みになるのは、どの頂点でしょう。

今回わかること

  • 行き止まりまで奥へ進み、戻ってから別の隣へ行く流れ
  • 再帰の呼び出し (道) が積み重なって戻る様子と、訪問済みの印が要る理由

前提

  • スタック (最後に入れた物から出る)
  • 再帰 (自分を呼ぶ関数)
  • グラフの頂点と辺

どんなたどり方か

深さ優先探索 (DFS) は、開始の頂点から行ける所まで奥へ進み、行き止まりになったら一つ前へ戻って、別の隣を試すたどり方です。迷路で、分かれ道を一本ずつ奥まで確かめるのと同じです。

いま通っている道は、スタックと同じ形で積み重なります。最後に進んだ頂点から先に戻るので、再帰の呼び出しでそのまま書けます。一度訪れた頂点に印を付けておくと、輪のあるグラフでも同じ頂点を何度も調べません。

手順と具体例

  1. 1開始の頂点を訪問済みにして、道に積む
  2. 2いちばん奥の頂点の隣を、名前の順に 1 つ見る
  3. 3まだの隣があれば、訪問済みにして道に積み、2 に戻る
  4. 4隣を全部見終えたら、道から外して 1 つ手前へ戻る
  5. 5道が空になるまで、2 から 4 をくり返す
「木」を A から始めたとき
動き道 (下から)訪れた順
A から始めるAA
A から B へ進むA、BA B
B から D へ進むA、B、DA B D
D から戻るA、BA B D
B から E へ進むA、B、EA B D E
E から戻るA、BA B D E
B から戻るAA B D E
A から C へ進むA、CA B D E C
C から F へ進むA、C、FA B D E C F
F から戻るA、CA B D E C F
C から G へ進むA、C、GA B D E C F G
G から戻るA、CA B D E C F G
C から戻るAA B D E C F G
A から戻る空A B D E C F G

見る回数と計算量

数えるもの回数計算量
訪問済みにして道に積む頂点 1 個につき 1 回O(V)
辺を見る辺 1 本につき 2 回 (両側から)O(E)
合わせて頂点の数と辺の数の和に比例O(V + E)

V は頂点の数、E は辺の数です。木の見本は頂点 7 個、辺 6 本なので、積むのが 7 回、辺を見るのが 12 回です。幅優先探索と回数は同じで、違うのは訪れる順だけです。道の長さは最大で V になるので、とても深いグラフを再帰で書くと、言語の呼び出しの深さの上限に当たることがあります。隣を名前の順に並べ替える手間は、図を追いやすくするための物で、ここでは数えていません。

使いどころ

向いているとき

  • 行ける所を全部調べたいとき (つながっている塊の数え上げ)
  • ある道を奥まで試して、だめなら戻りたいとき (迷路、組み合わせの全探索)
  • 頂点の行き止まりの順を使いたいとき (作業の順番を決める並べ替え)

向いていないとき

  • 辺の数で数えて、いちばん近い道を求めたいとき (幅優先探索を使う)
  • 辺ごとに長さが違う最短の道を求めたいとき (ダイクストラ法を使う)

コード

疑似コード

  1. 1visit(s) を呼ぶ (s は開始の頂点)
  2. 2visit(u) は、まず u を訪問済みにする
  3. 3u の隣 v を、名前の順に 1 つずつ見る
  4. 4まだの v は、visit(v) を呼んで奥へ進む
  5. 5訪問済みの v は、何もしない
  6. 6隣を全部見終えたら、呼び出し元へ戻る

実際のコード

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

Python と JavaScript は、書き換えて「実行」を押すと、その場で結果が出ます。Java は読むだけの欄です。

よくある疑問

幅優先探索と、訪れる順はどう違いますか?
幅優先探索は開始の頂点に近い順、深さ優先探索は行ける所まで奥へ進む順です。同じ木でも、幅優先は A B C D E F G、深さ優先は A B D E C F G になります。
再帰を使わずに書けますか?
道を自分でスタックに持てば書けます。ただし、頂点を積むときではなく取り出すときに印を付ける形にすると、訪れる順が再帰と変わることがあります。初めは再帰の形が読みやすいです。
なぜ戻る歩があるのですか?
隣を全部見終えた頂点は、もう奥へ進めません。道から外して、1 つ手前の頂点に戻り、そこで残っている隣を見ます。この戻りが、再帰の関数が終わって呼び出し元へ戻ることにあたります。

関連