幅優先探索

幅優先探索（はばゆうせんたんさく、）はグラフ理論()において木構造()やグラフ()の探索に用いられるアルゴリズム。アルゴリズムは根ノードで始まり隣接した全てのノードを探索する。それからこれらの最も近いノードのそれぞれに対して同様のことを繰り返して探索対象ノードをみつける。「横型探索」とも言われる。
幅優先探索は解を探すために、グラフの全てのノードを網羅的に展開・検査する。最良優先探索とは異なり、ノード探索にヒューリスティクスを使わずに、グラフ全体を目的のノードがみつかるまで、目的のノードに接近しているかどうかなどは考慮せず探索する。
アルゴリズム.
ノードの展開により得られる子ノードはキューに追加される。訪問済みの管理は配列やセットなどでも行える。
擬似コード.
v は頂点。
 function 幅優先探索(v)
 Q ← 空のキュー
 v に訪問済みの印を付ける
 v を Q に追加
 while Q が空ではない do
 v ← Q から取り出す
 v を処理する
 for each v に接続している頂点 i do
 if i が未訪問 then
 i に訪問済みの印を付ける
 i を Q に追加
アルゴリズムの特徴.
時間計算量.
最悪の場合、幅優先探索は全ての経路を考慮に入れる必要があるので、幅優先探索の時間計算量はO(|E|)である。ここで|E|はグラフ内の辺の数である。
空間計算量.
見つかったノードを全て記録する必要があるので、幅優先探索の空間計算量はO(|V|)となる。ここで|V|はグラフ内のノードの数である。または、formula_1ということができる。Bは枝分かれの最大数で、Mは木の最長経路の長さ。指数関数故に、幅優先探索は大量の情報から探索する事に向かない根拠になる。
完全性.
幅優先探索は完全である。つまり解が存在する場合はグラフの構造によらず、解をみつけることができる。しかしグラフが無限で探索対象の解が存在しない場合、幅優先探索は終了しない。
最適性.
一般に幅優先探索は最適で、常に開始ノードと終了ノードの長さが最も少ない辺を返す。もしグラフが重みつきならば、最初のノードの隣のノードが最良のゴールとは限らないが、この問題は辺のコストを考慮に入れた均一コスト探索()で解決できる。
幅優先探索の応用.
幅優先探索はグラフ理論における多くの問題を解くために使うことができる。以下は一例である。
参照透過な関数型言語の場合.
参照透過な関数型言語の場合は余再帰と遅延評価を使うと簡潔に書ける。下記は Scala の場合で、Scalaz の unfold が余再帰で、Stream が遅延リスト。
import scala.collection.immutable.Queue
import scalaz.Scalaz.unfold
case class Tree[T](value: T, children: Seq[Tree[T]])
def breadthFirstSearch[T](root: Tree[T]): Stream[T] =
 unfold(Queue(root))(_.dequeueOption.map { case (tree, queue) => (tree.value, queue ++ tree.children) })
Pythonによる幅優先探索アルゴリズムの実装.
 import networkx as nx
 import collections 
 def bfs(G):
 V = [ v for v in G.nodes() ]
 Q = collections.deque([V[0]]) 
 searched = { v: False for v in G.nodes() } 
 searched[V[0]] = True
 T = []
 while len(Q) != 0:
 v = Q.popleft() 
 edges = [ (v, i ) for (v, i) in G.edges(v) if not searched[i] ]
 for edge in edges:
 v, i = edge
 Q.append(i)
 T += [ (v, i) ]
 searched[i] = True
 return T
Grid 描画.
G = nx.grid_2d_graph(4,3)
nx.draw_networkx(G, pos=p, node_color='lightgray', node_size=1300, with_labels=True)
plt.axis('off')
plt.show()
幅優先による探索結果描画.
V = [ v for v in G.nodes() ]
bfst = bfs(G)
DG = nx.DiGraph()
DG.add_edges_from(bfst)
nx.draw_networkx(DG, edgelist=bfst, pos=p, node_color='lightgray', node_size=1500, with_labels=True)
plt.axis('off')
plt.show()