LoginSignup
4
0

More than 3 years have passed since last update.

迷路の最短経路探索を実装する

Last updated at Posted at 2019-12-20

この記事はtomowarkar ひとりAdvent Calendar 2019の20日目の記事です。

はじめに

幅優先探索とか、深さ優先探索とかなんか難しそうで今まであまり触れてこなかったんですが、なんとなくやる気になったので迷路の最短経路探索を書いてみました。

内容自体はよく出てくるような内容で、特段変なことはしてないのですが
備忘録兼アウトプットとして書いていきます。

成果物

image.png

コード

def printMaze(maze):
    """迷路を描画"""
    for i in range(maze["height"]):
        row = maze["data"][i * maze["width"] : (i + 1) * maze["width"]]
        print(" ".join(row))


def at(x, y, maze):
    """迷路の(x, y)座標のオブジェクトを返す"""
    return maze["data"][y * maze["width"] + x]


def setChar(x, y, char, maze):
    """迷路の(x, y)座標のオブジェクトを設定する"""
    arr = list(maze["data"])
    arr[y * maze["width"] + x] = char
    maze["data"] = "".join(arr)


if __name__ == "__main__":
    maze = {
        "width": 43,
        "height": 35,
        "data": "###########################################S #   #           #                       ## ### ########### # ################# ### ##   #           # # #               # # # #### ########### # # ##### ##### ### ### # ## #         # # #       # #   # # #     # ## ####### ### # ####### ### ### # ##### # ##         #       #         #     #     # ###### ### ##### ### ##### ### ##### ##### ##   # # # #   # #   # #   #   #     #     ## # # # # ### # # ### ### # ### ### # ### ## # # # #   # # # #     # # #   # # # # # ## # # # ##### # ####### ### # ### # # # # ## # # #       #       #     # #   # # # # ## # # # ##### ### ### ####### ### ### # # ## # # #   # #   # # #           #     # # ## # # ##### ### ### ########### ### ### # ## # #         #             # # # # #   # ## # ##### ### # ### ### ##### # # # ### # ## #     # # # # # #   # #       # #   # # ## ##### ### # ### ##### ### ### # ### # # ##     #     #               # # #   # #   #### # # ### ### ######### ### ### ### ### ## # # # # # #   #       # #             # ## ### ### ### ##### # ### ### ####### ### ##             #   # # #     # #   #   #   ## ### ####### ### ### # ##### ### ### # #### # # # #       #     # #       #   # #   ## # # # ### ### # ##### ##### ##### # ### ## # # # #   # # # #         #       #   # ## # # # # ### ### # # ####### ##### ### # ## # #   # #       # # #       #   # #   # ## # ##### # ####### # ######### ### # ### ## #       #         #               # #   G###########################################",
    }
    start = [0, 1]
    goal = [42, 33]

    visited = [False] * maze["width"] * maze["height"]
    costs = [999999] * maze["width"] * maze["height"]

    # スタート位置(0, 1)をqueueに入れる
    queue = [start]
    costs[start[1] * maze["width"] + start[0]] = 0

    while len(queue) > 0:
        # queueから位置を取り出す
        p, queue = queue[0], queue[1:]
        visited[p[1] * maze["width"] + p[0]] = True

        # 上下左右を検証
        for i in [[0, -1], [0, 1], [-1, 0], [1, 0]]:
            x, y = p[0] + i[0], p[1] + i[1]
            if x < 0 or y < 0 or x > maze["width"] - 1 or y > maze["height"] - 1:
                continue

            if at(x, y, maze) == " " and visited[y * maze["width"] + x] == False:
                queue.append([x, y])
                costs[y * maze["width"] + x] = costs[p[1] * maze["width"] + p[0]] + 1
            if at(x, y, maze) == "G":
                queue = []
                costs[y * maze["width"] + x] = costs[p[1] * maze["width"] + p[0]] + 1
                break
    # costsをゴールから辿り、最短経路を求める
    point = goal
    cost = costs[point[1] * maze["width"] + point[0]]
    while cost != 1:
        for i in [[0, -1], [0, 1], [-1, 0], [1, 0]]:
            x, y = point[0] + i[0], point[1] + i[1]
            if x < 0 or y < 0 or x > maze["width"] - 1 or y > maze["height"] - 1:
                continue
            if costs[y * maze["width"] + x] == cost - 1:
                cost = costs[y * maze["width"] + x]
                point = [x, y]
                setChar(x, y, ".", maze)
                break

    printMaze(maze)

解説

迷路情報を辞書型で定義します。
"#"が壁" "が通路, "S", "G"がそれぞれスタートとゴールという形です。
同時にスタートとゴールの(x, y)座標も手入力で入れてしまいます。

maze = {
    "width": 43,
    "height": 35,
    "data": "###########################################S #   #           #                       ## ### ########### # ################# ### ##   #           # # #               # # # #### ########### # # ##### ##### ### ### # ## #         # # #       # #   # # #     # ## ####### ### # ####### ### ### # ##### # ##         #       #         #     #     # ###### ### ##### ### ##### ### ##### ##### ##   # # # #   # #   # #   #   #     #     ## # # # # ### # # ### ### # ### ### # ### ## # # # #   # # # #     # # #   # # # # # ## # # # ##### # ####### ### # ### # # # # ## # # #       #       #     # #   # # # # ## # # # ##### ### ### ####### ### ### # # ## # # #   # #   # # #           #     # # ## # # ##### ### ### ########### ### ### # ## # #         #             # # # # #   # ## # ##### ### # ### ### ##### # # # ### # ## #     # # # # # #   # #       # #   # # ## ##### ### # ### ##### ### ### # ### # # ##     #     #               # # #   # #   #### # # ### ### ######### ### ### ### ### ## # # # # # #   #       # #             # ## ### ### ### ##### # ### ### ####### ### ##             #   # # #     # #   #   #   ## ### ####### ### ### # ##### ### ### # #### # # # #       #     # #       #   # #   ## # # # ### ### # ##### ##### ##### # ### ## # # # #   # # # #         #       #   # ## # # # # ### ### # # ####### ##### ### # ## # #   # #       # # #       #   # #   # ## # ##### # ####### # ######### ### # ### ## #       #         #               # #   G###########################################",
}
start = [0, 1]
goal = [42, 33]

下準備

探索に用いるqueuevisited, 最短経路を導く際に用いるcostsを定義します。

迷路は二次元配列で出力しますが、データ加工は全て一次元配列でおこなうので、適宜変換を行う必要があります。


visited = [False] * maze["width"] * maze["height"]
costs = [999999] * maze["width"] * maze["height"]

# スタート位置(0, 1)をqueueに入れる
queue = [start]
costs[start[1] * maze["width"] + start[0]] = 0

探索メイン処理

幅優先探索なので、キューの頭から取り出して検証していきます。
上下左右の状態が通路であればどんどんキューに追加していきます。
コストの更新は常に基準点のコストから+1していくことで、最短経路を導出する際に使用することができます。

    while len(queue) > 0:
        # queueから位置を取り出す
        p, queue = queue[0], queue[1:]
        visited[p[1] * maze["width"] + p[0]] = True

        # 上下左右を検証
        for i in [[0, -1], [0, 1], [-1, 0], [1, 0]]:
            x, y = p[0] + i[0], p[1] + i[1]
            if x < 0 or y < 0 or x > maze["width"] - 1 or y > maze["height"] - 1:
                continue

            if at(x, y, maze) == " " and visited[y * maze["width"] + x] == False:
                queue.append([x, y])
                costs[y * maze["width"] + x] = costs[p[1] * maze["width"] + p[0]] + 1
            if at(x, y, maze) == "G":
                queue = []
                costs[y * maze["width"] + x] = costs[p[1] * maze["width"] + p[0]] + 1
                break

最短経路導出

costはスタートからかかる距離と同意であるため、ゴール位置のコストから0まで遡っていくことで最短経路が導出できます。

    # costsをゴールから辿り、最短経路を求める
    point = goal
    cost = costs[point[1] * maze["width"] + point[0]]
    while cost != 1:
        for i in [[0, -1], [0, 1], [-1, 0], [1, 0]]:
            x, y = point[0] + i[0], point[1] + i[1]
            if x < 0 or y < 0 or x > maze["width"] - 1 or y > maze["height"] - 1:
                continue
            if costs[y * maze["width"] + x] == cost - 1:
                cost = costs[y * maze["width"] + x]
                point = [x, y]
                setChar(x, y, ".", maze)
                break

    printMaze(maze)

結果

image.png

おわりに

以上明日も頑張ります!!
tomowarkar ひとりAdvent Calendar Advent Calendar 2019

参考

https://qiita.com/nati-ueno/items/a789095aff0aec10d5d0
https://vigne-cla.com/18-2/

4
0
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
4
0