幅優先探索
幅優先探索(Breadth First Search)とは、グラフを始点から近い順に1つずつ調べていく探索法です。キューを使うことにより、FIFOで全てを探索するというイメージで理解しています。
迷路の幅優先探索、また深さ優先探索については、以下の資料が大変分かりやすいです。
京都産業大学 発展プログラミング演習I 演習資料 迷路の経路探索
アルゴリズム
- 根ノードを空のキューに加える。
- ノードをキューの先頭から取り出し、以下の処理を行う。
- ノードが探索対象であれば、探索をやめ結果を返す。
- そうでない場合、ノードの子で未探索のものを全てキューに追加する。
- もしキューが空ならば、グラフ内の全てのノードに対して処理が行われたので、探索をやめ”not found”と結果を返す。
- 2に戻る。
ノードの展開により得られる子ノードはキューに追加される。訪問済みの管理は配列やセットなどでも行える。
迷路にアルゴリズムを当てはめる
- スタート地点を空のキューに加える。
- 現在地をキューの先頭から取り出し、以下の処理を行う。
- 現在地がゴールであれば、探索をやめ結果を返す。
- そうでない場合、現在地の上下左右、未探索かつ通路を全てキューに追加する。
- もしキューが空ならば、全ての通路を通ったがゴールにたどり着けなかったということなので、探索をやめ”ゴールが見つかりませんでした。”と結果を返す。 今回は必ずゴールが見つかる迷路とする。
- 2に戻る。
今回は、未探索の地点をカウントアップしていくことで最短経路も求める。
pythonで実装
pythonには、queueを取り扱うのに便利なdeque オブジェクトがあるので、こちらを使います。
from collections import deque
import colorama
import termcolor
import maze
PATH = 0
WALL = 1
width = 29
height = 29
# 迷路の配列を作成する。
# 壁1、通路0、スタート[1, 1]、ゴール[height-1, widht-1]として、
# 迷路をランダムに作成して二次元のリストを返す。
maze = maze.MazeByMakingWall(width, height).make_maze()
# 地図上の未訪問地点を-1とする。
# また、移動するごとに+1することで、スタート地点からの移動距離を保持する。
distance_visited = list()
for x in range(height):
distance_visited.append([-1] * width)
# スタート地点を登録する。
queue = deque([[1, 1]])
distance_visited[1][1] = 0
while True:
x, y = queue.popleft()
# ゴールの場合は終了する。
if maze[x][y] == 'G':
goal_x, goal_y = x, y
break
# 現在地点の上下左右のマスを確認する。
for i, j in ([1, 0], [-1, 0], [0, 1], [0, -1]):
new_x, new_y = x + i, y + j
# それぞれのマスが移動できる場合、かつまだ移動したことが無い場合
if maze[new_x][new_y] != WALL and distance_visited[new_x][new_y] == -1:
# そのマスのスタートからの移動距離を設定し、またキューに登録する。
distance_visited[new_x][new_y] = distance_visited[x][y] + 1
queue.append([new_x, new_y])
# ゴールからスタートまでのルートをゴールから逆算する。
route = list()
route.append([goal_x, goal_y])
while True:
x, y = route[-1]
distance = distance_visited[x][y]
if distance == 0:
break
for i, j in ([1, 0], [-1, 0], [0, 1], [0, -1]):
before_x, before_y = x + i, y + j
if distance_visited[before_x][before_y] == distance-1:
route.append([before_x, before_y])
break
for x, y in route:
if maze[x][y] == 'S' or maze[x][y] == 'G':
continue
else:
maze[x][y] = 'R'
# 色付きで出力する。
colorama.init()
for row in maze:
for cell in row:
if cell == PATH:
print(' ', end='')
elif cell == WALL:
print(termcolor.colored(' ', 'green', 'on_green'), end='')
elif cell == 'R':
print(termcolor.colored(' ', 'yellow', 'on_yellow'), end='')
elif cell == 'S':
print(termcolor.colored('STA', 'red'), end='')
elif cell == 'G':
print(termcolor.colored('GOA', 'red'), end='')
print()