Pythonでグリッド上の最短経路を探索する:BFSによる最短移動回数の求め方
はじめに
本記事では、記号が書かれたグリッド(マス目)の中で、現在位置からゴールまでの最短移動回数を求めるPythonプログラムを紹介します。この種の迷路・経路探索問題は、幅優先探索(BFS)を用いることで効率的に解くことができます。
グリッドの記号の意味
グリッドには以下の4種類の記号が含まれ、それぞれ次のような意味を持ちます。
- # … ゴールとなるセル。ここへの到達を目指します。
- O … 自由に通行できる空きセル。
- * … 自分の現在位置。
- X … 障害物のあるセル。通過することはできません。
問題の概要
与えられたグリッド上で、現在位置「*」からゴール「#」まで到達するために必要な最小の移動回数を求めます。移動は上下左右の4方向に1マスずつ行えるものとし、ゴールに到達できない場合は -1 を返します。
例として、次のような入力を考えてみましょう。
| X | X | O | X |
| X | X | * | X |
| X | # | O | X |
| X | X | X | X |
この場合、現在位置から下へ2マス進めばゴールに届くため、出力は 2 となります。
解き方のアルゴリズム(幅優先探索)
最短距離を求める問題では、BFS(幅優先探索)が有効です。手順は以下の通りです。
- グリッドの行数を m、列数を n とします。
- グリッド全体を走査し、「*」の位置 (i, j) を特定します。
- 答えをカウントする変数 ans を 0 で初期化し、キューに (i, j) を追加します。また、訪問済みを示すため、そのセルを「X」に書き換えます。
- キューが空になるまで、以下を繰り返します。
- ans を 1 増やします(1ステップ分の移動を表す)。
- 新しいキュー newq を用意します。
- 現在のキュー内の各セルについて、上下左右の隣接セル (ii, jj) を順に調べます。
- 隣接セルがグリッドの範囲内であり、かつ「X」でない場合は、
- そのセルが「#」(ゴール)であれば、ans を返して終了します。
- そうでなければ、(ii, jj) を newq の末尾に追加し、そのセルを「X」に書き換えて再訪問を防ぎます。
- 処理が終わったら queue を newq で置き換えます。
- キューが空になってもゴールに到達しなかった場合は、-1 を返します。
既に訪れたセルを「X」に置き換えることで、同じセルを二度以上探索することがなくなり、計算量は O(m × n) に抑えられます。
実装例
それでは、実際のPythonコードを見てみましょう。
def solve(grid):
m, n = len(grid), len(grid[0])
for i in range(m):
for j in range(n):
if grid[i][j] == "*": break
else: continue
break
ans = 0
queue = [(i, j)]
grid[i][j] = "X"
while queue:
ans += 1
newq = []
for i, j in queue:
for ii, jj in (i-1, j), (i, j-1), (i, j+1), (i+1, j):
if 0 <= ii < m and 0 <= jj < n and grid[ii][jj] != "X":
if grid[ii][jj] == "#": return ans
newq.append((ii, jj))
grid[ii][jj] = "X"
queue = newq
return -1
print(solve([['X', 'X', 'O', 'X'],['X', 'X', '*', 'X'],['X', '#',
'O', 'X'],['X', 'X', 'X', 'X']]))
入力
[['X', 'X', 'O', 'X'], ['X', 'X', '*', 'X'], ['X', '#', 'O', 'X'], ['X', 'X', 'X', 'X']]
出力
2
まとめ
このように、BFSを利用すれば、障害物を避けながらゴールまでの最短移動回数を効率よく求めることができます。ポイントは、探索済みのセルを早めにマークして再訪問を防ぐこと、そして層ごと(ステップごと)にキューを更新することです。迷路探索やゲームAIの経路計算など、さまざまな場面で応用できる基本的なアルゴリズムなので、ぜひ理解しておきましょう。
-
Pythonでn分木の最長パスの長さを求めるプログラムの書き方
各要素が (u, v) という形式を持ち、u が v の親であることを表す辺リストが与えられているとします。このとき、木の中で最も長いパスの長さを求める必要があります。ここでいうパスの長さとは、「そのパスに含まれるノードの総数 + 1」のことです。 たとえば、入力が下図のような n 分木だった場合を考えてみましょう。 この場合の出力は 5 になります。なぜなら、パス [1, 4, 5, 7] には合計 4 つのノードが含まれており、パスの長さは 1 + 4 = 5 となるからです。 解き方のアプローチ この問題は、幅優先探索(BFS)を2回実行するという定番テクニックで効率よく解けます。まず
-
Pythonでグラフがすべての人にとって移動可能かどうかを確認するプログラム
n個の頂点(0からn-1までの番号が付けられたもの)から構成される無向グラフが与えられます。各辺には重みが設定されており、重みは「1」「2」「3」の3種類があります。このグラフを移動できるのはJackとCaseyの2人で、Jackは重み1の辺のみ、Caseyは重み2の辺のみを移動でき、重み3の辺は両方が移動できます。 ここで、JackとCaseyの両方がグラフ内のすべての頂点に到達できるようにするために、不要な辺を削除することを考えます。このとき削除が必要な辺の本数を求め、どのようにしても移動可能な状態にできない場合は-1を返します。 例えば、入力が次のような場合を考えてみましょう。 n =