76
55

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

アルゴリズム(DFS/BFSを1ステップずつ追いかけてみる)

Last updated at Posted at 2019-03-18

DFS / BFSとは

Wikipediaにはなんだか難しそうな事が書いてありますが、「経路を順にたどってゴールに到達するような問題」「ルールに基づいて一定範囲を塗りつぶすような問題」を実装する際に役に立つアルゴリズムです。迷路を解くとか一定エリアの広さを考えるといった問題が出てきたらまずはこの DFS / BFS が適用可能かどうか考えてみると良い 1 と思います。

「知らないと詰む」系のアルゴリズムですし、知ってると気持ちよく解ける 2 のでぜひ DP(動的計画法)の前にこちらを使い倒せるようにしてください。

迷路のような典型的な問題であればすぐに DFS / BFS を思いつくのですが、塗りつぶしや領域判定でこれが使えることは慣れないと思いつきにくいです。

実装イメージ

実装にあたって用意するものは下記の2点です。

  • 探索候補地点を格納する「探索候補スタック」
  • 探索済み地点を格納する「探索済みリスト」

これらを使って、DFS を組む時の具体的なロジックイメージは下記の通りです。※BFS を組む場合は「候補探索スタック」を「候補探索キュー」に、poppop(0) もしくは popleft(collections.deque の場合)に読み替えてください

探索スタート地点を「探索候補スタック」に積む
while スタックが空っぽになるまで
    スタックから次の探索地点を1つ取り出す(pop)
    「探索済みリスト」に取り出した地点を格納(ここが現在地点)
  現在地点から、次に行こうと思えば行けるポイントを全部探し出す
    for 地点 in 次に行こうと思えば行けるポイント
        if その地点は既に「探索済みリスト」にある?
            探索済みなので「探索候補スタック」には入れない(continue)
        if その地点は既に「探索候補スタック」にある?(閉路があるなら可能性あり、木なら可能性なし)
            探索予定なので「探索候補スタック」には入れない(continue)
        未探索かつ候補にも入っていない地点なので「探索候補スタック」に追加する(append)
print 探索済み地点

これをブン回せば、最後に出てくる「探索済みリスト」が探索の足跡を全部記録してくれています。

塗りつぶし面積などを求めたい場合はこうやってDFSすると探索の足跡サイズ(配列の大きさ)がそのまま面積になりますし、迷路の最短経路を求めたい場合は上記をキューに変更してBFSするとゴールまでの最短距離が分かります(ゴールに到着した瞬間に break)

DFS は再帰で実装する例もよく紹介されますが、個人的には DFS だろうと BFS だろうと同じロジックで実装できる、上記の「空っぽになるまで while でブン回す」方がシンプルでオススメです。

【重要】なんでこれでできるの?

無向グラフ

DFS / BFS の手法によってなぜ経路探索が無駄なく実現できるのか、なぜスタックだと深さ優先でキューだと幅優先が実現されるのかについては、ぜひ頭の中できちんと理解をしておいてください。特に「探索済みリスト」と「探索候補スタック」がどのように変化していくのかを 1 ステップ 1 ステップ理解することが重要です。

というわけで、上記のような地図を、1 から 6 まで DFS で辿る動きを追ってみましょう。ロジックイメージと地図を横に置いて、下記を読み進めてみてください。

while 1 ループ目(終了地点)

  • 現在位置:1
  • 探索済みリスト:( 1 )
  • 探索候補スタック:( 2, 5 )

スタート地点として「探索候補スタック」に積まれていた 1 が pop され、探索済みリストに格納されます。あわせて 1 から移動する先の候補として 2 と 5 が「探索候補スタック」に積まれます。

while 2 ループ目(終了地点)

  • 現在位置:5
  • 探索済みリスト:( 1, 5 )
  • 探索候補スタック:( 2, 4 )

「探索候補スタック」の一番後ろから 5 が pop され、探索済みリストに格納されます。5 から移動する先の候補は 1 と 2 と 4 ですが、1 は既に「探索済みリスト」に、2 は既に「探索候補スタック」に存在することから探索候補の対象外となり、4 だけが「探索候補スタック」に積まれます。

while 3 ループ目(終了地点)

  • 現在位置:4
  • 探索済みリスト:( 1, 5, 4 )
  • 探索候補スタック:( 2, 3, 6 )

「探索候補スタック」の一番後ろから 4 が pop され、探索済みリストに格納されます。4 から移動する先の候補は 3 と 5 と 6 ですが、5 は既に「探索済みリスト」に存在することから探索候補の対象外となり、3 と 6 が「探索候補スタック」に積まれます。

while 4 ループ目(終了地点)

  • 現在位置:6
  • 探索済みリスト:( 1, 5, 4, 6 )
  • 探索候補スタック:( 2, 3 )

「探索候補スタック」の一番後ろから 6 が pop され、探索済みリストに格納されます。6 から移動する先の候補は 4 ですが、4 は既に「探索済みリスト」に存在することから探索候補の対象外となり、「探索候補スタック」には何も積まれません。

while 5 ループ目(終了地点)

  • 現在位置:3
  • 探索済みリスト:( 1, 5, 4, 6, 3 )
  • 探索候補スタック:( 2 )

「探索候補スタック」の一番後ろから 3 が pop され、探索済みリストに格納されます。3 から移動する先の候補は 2 と 4 ですが、4 は既に「探索済みリスト」に、2 は既に「探索候補スタック」に存在することから探索候補の対象外となり、「探索候補スタック」には何も積まれません。

while 6 ループ目(終了地点)

  • 現在位置:2
  • 探索済みリスト:( 1, 5, 4, 6, 3, 2 )
  • 探索候補スタック:( )

「探索候補スタック」の一番後ろから 2 が pop され、探索済みリストに格納されます。2 から移動する先の候補は 1 と 3 と 5 ですが、全て「探索済みリスト」に存在することから探索候補の対象外となり、「探索候補スタック」には何も積まれません。

ここで探索候補スタックが空っぽになりますので while ループは終了します。

  1. 競技プログラミングでは「これって DFS / BFS でいけるんじゃ!?」という勘所やひらめきが一番重要だったりしますが、数をこなせばきっと身に付きます(と信じてます)

  2. 「俺のプログラムが問題を解いてくれた!」と個人的に感動した最初のアルゴリズムです。問題もいかにもパズルっぽいものが多くて楽しいイメージ

76
55
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
76
55

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?