深さ優先探索 (DFS) アルゴリズム
グラフに頂点が
n
個、辺が e
本あるとき、ある頂点から始めて到達できるすべての頂点を順番に訪問していきたいと想像してください。DFS アルゴリズムを使うと、ある頂点からスタートし、ある経路を行けるところまでたどってから戻り、まだ訪問していない別の経路を探検する、という手順でグラフ上の頂点を探査できます。
DFS アルゴリズムをイメージする方法として、「ロボットが目の前の頂点の隣接ノードと、すでに訪問した頂点の情報だけを把握しながらグラフを移動していく」と考えるとわかりやすいです。これは再帰的なプロセスで、毎回まだ訪問していない新しい頂点をひとつ選んでそこに移動し、そこが行き止まりになったら一つ前の頂点(再帰の戻り)に戻ってほかの隣接ノードを探査します。小さなロボットがグラフの中を探索している想像は、実装を考える際にとても役立ちます。
DFS の実装は、再帰を用いて行うことができ、現在の頂点(
v
)と訪問済みの頂点を記録する配列(used
)を管理します:import sys
sys.setrecursionlimit(10 ** 6) # DFSが正しく動作するよう再帰の限界を引き上げる
n, e = map(int, input().split()) # n: 頂点数, e: 辺数
g = [[] for _ in range(n)] # グラフ - 隣接リスト
used = [False] * n # 訪問済み頂点の管理
for _ in range(e): # 辺を読み込む
a, b = map(int, input().split()) # a -> b と b -> aを結ぶ
g[a].append(b)
g[b].append(a)
def dfs(v): # 深さ優先探索
used[v] = True # 頂点を訪問済みにマーク
print(v, end=' ') # 頂点を出力
for to in g[v]: # vに隣接する各頂点に対して
if not used[to]: # まだ訪問されていなければ
dfs(to) # その頂点に対してdfsを呼び出す
start = int(input()) # 開始頂点
dfs(start) # 開始頂点に対してdfsを呼び出す
まず最初にグラフを初期化し、
start
頂点からグラフ探索を始めます。最初はすべての used
が False
(未訪問)に設定されており、頂点を訪問すると used[v] = True
で印をつけて、その頂点に隣接するすべてのノードへ移動します。再帰を用いることで、ある経路を限界までたどったあと、前の頂点に戻って別の隣接ノードを探索する「戻り操作」が簡潔に表現できます。再帰から「戻る」ことによって、前の頂点に戻るイメージです。
いくつかの入力例に対するアルゴリズムの動作を見てみましょう:
n = 4, e = 4
The input
a, b
pairs are the following: [(0, 1), (0, 2), (1, 2), (3, 2)]The starting vertex is 3
v = 3
→used = [False, False, False, True]
- 3 のすべての隣接ノードを探索 ⇒
[2]
v = 2
→used = [False, False, True, True]
- 2 のすべての隣接ノードを探索 ⇒
[0, 1]
v = 0
→used = [True, False, True, True]
- 0 のすべての隣接ノードを探索 ⇒
[1, 2]
v = 1
→used = [True, True, True, True]
- 1 のすべての隣接ノードを探索 ⇒
[0, 2]
⇒ すべて訪問済み
- 1 から戻る
- 0 から戻る
- 2 から戻る
- 3 から戻る ⇒ DFS 終了
- 完了
n = 5, e = 4
The input
a, b
pairs are the following: [(0, 1), (0, 2), (1, 2), (3, 4)]The starting vertex is 0
v = 0
→used = [True, False, False, False, False]
- 0 のすべての隣接ノードを探索 ⇒
[1, 2]
v = 1
→used = [True, True, False, False, False]
- 1 のすべての隣接ノードを探索 ⇒
[0, 2]
v = 2
→used = [True, True, True, False, False]
- 2 のすべての隣接ノードを探索 ⇒
[0, 1]
⇒ すべて訪問済み
- 2 から戻る
- 1 から戻る
- 0 から戻る ⇒ DFS 終了
- 完了
Notice how nodes 3 and 4 were never visited.
They were not connected to the initial node 0.
n = 10, e = 11 (example from the video)
The input
a, b
pairs are the following: [(0, 1), (0, 2), (1, 2), (1, 4), (1, 3), (3, 5), (6, 5), (5, 8), (5, 7), (7, 8), (8, 9)]The starting vertex is 0
v = 0
→used = [T, F, F, F, F, F, F, F, F, F]
⇒ 隣接ノードを探索[1, 2]
v = 1
→used = [T, T, F, F, F, F, F, F, F, F]
⇒ 隣接ノードを探索[0, 2, 3, 4]
v = 2
→used = [T, T, T, F, F, F, F, F, F, F]
⇒ 隣接ノードを探索[0, 1]
- 2 から戻る
v = 1
→used = [T, T, T, F, F, F, F, F, F, F]
⇒ 隣接ノードを探索[x, x, 3, 4]
v = 3
→used = [T, T, T, T, F, F, F, F, F, F]
⇒ 隣接ノードを探索[1, 5]
v = 5
→used = [T, T, T, T, F, T, F, F, F, F]
⇒ 隣接ノードを探索[3, 6, 7, 8]
v = 6
→used = [T, T, T, T, F, T, T, F, F, F]
⇒ 隣接ノードを探索[5]
- 6 から戻る
v = 7
→used = [T, T, T, T, F, T, T, T, F, F]
⇒ 隣接ノードを探索[5, 8]
v = 8
→used = [T, T, T, T, F, T, T, T, T, F]
⇒ 隣接ノードを探索[5, 7, 9]
v = 9
→used = [T, T, T, T, F, T, T, T, T, T]
⇒ 隣接ノードを探索[8]
- 8 から戻る
- 7 から戻る
- 6 から戻る
- 5 から戻る
- 3 から戻る
v = 1
→used = [T, T, T, T, F, T, T, T, T, T]
⇒ 隣接ノードを探索[x, x, x, 4]
v = 4
→used = [T, T, T, T, T, T, T, T, T, T]
⇒ 隣接ノードを探索[1]
- 4 から戻る
- 1 から戻る
- 0 から戻る
- 完了
チャレンジ: グラフの連結成分の個数を数える
無向グラフにおいて、
v
個の頂点と e
本の辺が与えられたとき、そのグラフ内の連結成分の数を求める問題です。頂点の集まりが連結成分であるとは、どの頂点からでも同じ集まりの任意の頂点へ移動できることを指します。 入力
入力の最初の行に、二つの整数
v
(1 ≤ v ≤ 100 000) と e
(1 ≤ e ≤ 100 000) が与えられます。続く
e
行には v1
, v2
という 2 つの整数 (1 ≤ v1, v2 ≤ v) が書かれており、それらが示す頂点同士が相互に接続されていることを表します。 出力
グラフに含まれる連結成分の個数を出力してください。
例
入力 | 出力 |
7 6
1 2
2 3
1 3
4 5
5 6
4 6 | 3 |
2 1
1 2 | 1 |
2 0 | 2 |
Constraints
Time limit: 5 seconds
Memory limit: 512 MB
Output limit: 1 MB