Python + ダイクストラ法 ( Dijkstra algorithm )で最短経路を求める

Python + ダイクストラ法 ( Dijkstra's algorithm )で最短経路を求めます.

# 問題

下記のネットワークでnode1からnode5までの最短経路を求めます.

# ネットワークの作成

上画像より,正方行列でネットワーク情報を作成します

本稿では,nodeが5つあるので,5*5の行列で記述します.

行列内の要素は,今回は距離を入力する.交通容量を記入する場合や,台・kmも記入する場合もあります.

import math

# ネットワークを生成
# 初期のnode間の距離のリスト
route_list = [[0, 50, 80, 0, 0], #node-0
              [0, 0, 20, 15, 0 ] , #node-1
              [0, 0, 0, 10, 15], 
              [0, 0, 0, 0, 30], 
              [0, 0, 0, 0, 0]]

# nodeの数
node_num = len(route_list)

# ダイクストラ法の計算結果の保存

ダイクストラ法では,全経路を探索し最短距離の経路を後から選択するので,計算過程を保存する必要があります.

本コードでは,「未探索ノード・各nodeへの最短距離・各nodeをゴールとした場合,最終通過node」を格納するリストを作成します.

# 未探索ノード
unsearched_nodes = list(range(node_num))

# node-0から各nodeに到達する最短距離リスト
# node-0からstartは0とする
distance = [math.inf] * node_num
distance[0] = 0

# node-0から最短経路で各nodeに到達する場合,直前のnodeリスト
previous_nodes = [-1] * node_num

上記のコードだけではイメージがつきづらいので,先に上記の変数の最終結果を出力します.

# unsearched_nodesの最終結果
print('未探索ノードを下記に示す')
print([])
print("="*40)

# distanceの最終結果
distance_out = [0, 50, 70, 65, 85]
print('各nodeにおける,node-0からの最短経路')
for i in range(len(distance_out)):
    print(f"node-0からnode-{i + 1}までの最短距離 = {distance_out[i]}")
print("="*40)

# previous_nodesの最終結果
previous_nodes_out = [-1, 0, 1, 1, 2]
print('node-0から最短経路で各nodeに到達する場合の手前のnode')
for i in range(len(previous_nodes_out)):
    print(f"node-{i + 1}へ到達する手前のnode = {previous_nodes_out[i] + 1}")
    
# 出力
# 未探索ノードを下記に示す
# []
# ========================================
# 各nodeにおける,node-0からの最短経路
# node-0からnode-1までの最短距離 = 0
# node-0からnode-2までの最短距離 = 50
# node-0からnode-3までの最短距離 = 70
# node-0からnode-4までの最短距離 = 65
# node-0からnode-5までの最短距離 = 85
# ========================================
# node-0から最短経路で各nodeに到達する場合の手前のnode
# node-1へ到達する手前のnode = 0
# node-2へ到達する手前のnode = 1
# node-3へ到達する手前のnode = 2
# node-4へ到達する手前のnode = 2
# node-5へ到達する手前のnode = 3

下画像と各print文を見比べてください.

# ダイクストラ法の計算結果の解釈

ダイクストラ法の計算結果の解釈は,下記の通りです.

# ダイクストラ法の実行

下記のコードでダイクストラ法による最短経路探索を実施します.

import time
start_time = time.perf_counter()

# 未探索ノード
unsearched_nodes = list(range(node_num))

# node-0から各nodeに到達する最短距離リスト
# node-0からstartは0とする
distance = [math.inf] * node_num
distance[0] = 0

# node-0から最短経路で各nodeに到達する場合,直前のnodeリスト
previous_nodes = [-1] * node_num

# 任意の最短移動距離で到達できる未探索node
def get_unsearched_next_node(moving_distance, 
                                 node_distance_list, 
                                 unsearched_nodes):
    search_idx = 0
    while True:
        min_node = node_distance_list.index(moving_distance, search_idx)
        found = min_node in unsearched_nodes
        if found:
            return min_node
        else:
            search_idx = search_idx + 1

#未探索ノードがなくなるまで繰り返す
while(len(unsearched_nodes) != 0):
    # STEP *-1 START
    # 現状移動させることができる最小距離
    posible_min_distance = math.inf # 初期値=inf
    # 未探索のノード内で移動最小距離を探索
    for node_index in unsearched_nodes:
        # より短い経路が見つかれば更新
        if posible_min_distance > distance[node_index]: 
            posible_min_distance = distance[node_index]
    
    # 移動最小距離で到達できる未探索のnodeを出力
    next_node = get_unsearched_next_node(posible_min_distance, 
                                         distance, 
                                         unsearched_nodes)
    # STEP *-1 END
    
    # STEP *-2 START
    # 到達できるnodeを未探索リストから除去
    unsearched_nodes.remove(next_node)

    # 到達できるnodeからのリンク(距離)を抽出
    next_node_link = route_list[next_node]
    # STEP *-2 END
    
    # STEP *-3 START
    for idx, link_dis in enumerate(next_node_link):
        # route_dis == 0は繋がっていないをことを示す
        if link_dis != 0:
            # ここでは各nodeへの最短距離を更新
            if distance[idx] > (distance[next_node] + link_dis):
                distance[idx] = distance[next_node] + link_dis # 過去に設定されたdistanceよりも小さい場合はdistanceを更新
                previous_nodes[idx] =  next_node # ひとつ前に到達するノードのリストも更新
    # STEP *-3 END
    
execution_time = time.perf_counter() - start_time
print(execution_time)

# 実行時間
# 0.002838199958205223

下記のコードで結果を表示します.

print("-----経路-----")
previous_node = node_num - 1
while previous_node != -1:
    if previous_node !=0:
        print(str(previous_node) + " <- ", end='')
        # 最初に goal地点の'5 <- 'が出力
    else:
        print(str(previous_node))
    previous_node = previous_nodes[previous_node]

print("-----距離-----")
print(distance[node_num - 1])

# 出力
# -----経路-----
# 4 <- 2 <- 1 <- 0
# -----距離-----
# 85

# まとめ

Python + ダイクストラ法 ( Dijkstra's algorithm )で最短経路を求めました.

# 参考サイト

Python ダイクストラ法 ( Dijkstra's algorithm ) で最短経路を求める (opens new window)

PathFinding.js/visual (opens new window)

A*アルゴリズムとダイクストラ法の違い (opens new window)

pandasでテキストファイルを読み込む

pandasでテキストファイルを読み込む

pandasでテキストファイルを読み込みを実装します.

BigQueryでUTC(米国標準時)からJST(日本標準時)へ変更する

BigQueryでUTC(米国標準時)からJST(日本標準時)へ変更する

igQueryでUTC(米国標準時)からJST(日本標準時)へ変更します