Pythonで幅優先探索を実装する
アルゴリズムの勉強のために、幅優先探索を書いてみた。
使ったのはAtCoder Beginers Contest 007Cの問題。この頃はアルゴリズムがそのまま出題されてたようだ。
特殊事項として、この問題ではスタートからゴールまでは必ず行くことができる前提がある。さらに周り中が壁で囲まれているので、盤面からはみ出すのを考慮する必要がない。
実装
from collections import deque def bfs(maze, visited, sy, sx, gy, gx): queue = deque([[sy, sx]]) visited[sy][sx] = 0 while queue: y, x = queue.popleft() if [y, x] == [gy, gx]: return visited[y][x] for j, k in ([1, 0], [-1, 0], [0, 1], [0, -1]): new_y, new_x = y+j, x+k if maze[new_y][new_x] == "." and \ visited[new_y][new_x] == -1: visited[new_y][new_x] = visited[y][x] + 1 queue.append([new_y, new_x]) if __name__ == "__main__": R, C = map(int, input().split()) sy, sx = map(int, input().split()) gy, gx = map(int, input().split()) sy, sx, gy, gx = sy-1, sx-1, gy-1, gx-1 maze = [input() for i in range(R)] visited = [[-1]*C for j in range(R)] print(bfs(maze, visited, sy, sx, gy, gx))
たった25行で書けた。
原理としては、今いるマスから上下左右に隣り合うマスの座標をキューに入れるとともに、visitedリストにも記録していくという繰り返し。
キューは先入先出なので、今いるマスから上下左右に隣り合うマスを全て探索した後、それらの上下左右に隣り合うマスを探索する、というように探索を行っていく。
全マス調べるため時間はかかるが、ゴールへの最短ルートを確実に求められる。
速度アップの工夫
実をいうと、上記のコードは2度目の提出だった。
その前に書いたバージョンでは、bfs関数を以下のようにしていた。
def bfs(maze, visited, sy, sx, gy, gx): queue = deque([[sy, sx]]) count = 0 while queue: #ここでforループを回している for _ in range(len(queue)): coord = queue.popleft() visited[coord[0]][coord[1]] = 1 if coord == [gy, gx]: return count for j, k in ([1, 0], [-1, 0], [0, 1], [0, -1]): next_coord = [coord[0]+j, coord[1]+k] if maze[next_coord[0]][next_coord[1]] == "." and \ visited[next_coord[0]][next_coord[1]] != 1: queue.append(next_coord) count += 1
visitedリストの値を0=未訪問と1=訪問のみとしていたため、bfs関数の中のwhile queue
以下でforループを回して、ループを何回まわったかをカウントしスタートからの距離を求めていた。しかし、テストケースの最初の4分の1くらいで実行時間が2秒を超え、あえなくTLEに。
そこで、visitedリストの値にスタートからの距離を直接書かせることにした。まずスタートマスの値を0、未訪問のマスの値を-1としておく。そして新しいマスを探索するときは、visitedリストから前にいたマスの値を取り出し、それに1を足した値を新たに書けばよい。
こうすることで、forループをひとつ減らすことができ、実行時間も全体で25msと大幅に速くなった。リストをうまく使うことで、回数カウント目的のforループをなくすというテクニックは、他の問題でも応用できそうだ。