從turtle海龜動畫 學習 Python - 高中彈性課程系列 11.4 最短路徑 Dijkstra- 用 turtle 呈現演算法之執行動作

將與數學, 益智遊戲, 工業應用, 科學計算等相關的演算法, 例如 連分數演算法, 最短路徑演算法等, 練習以海龜移動呈現, 讓學生透過這個過程, 可以了解演算法的執行過程, 算是用海龜繪圖, 實現演算法視覺化.

  • Dijkstra algorithm, 是指定一個起點, 可以求出他到圖中每個點的最短路徑, 所以叫 單源最短路徑(single source shortest path) 演算法.
    要求圖中的路徑長(權重)不能是負的.

  • Bellman-Ford algorithm, 是與 Dijkstra 一樣的功能, 也是單源最短路徑演算法, 改進了, 允許圖中的路徑長(權重)可以是負的, 但是不允許圖中有負權重的迴路.

  • Floyd-Warshall algorithm, 則是可以得出所有兩對頂點(節點 node)之間的最短路徑(all pairs shortest path), 與Bellman-Ford的條件一樣, 允許圖中的路徑長(權重)可以是負的, 但是不允許圖中有負權重的迴路.

  • A* algorithm, 可以看成是Dijkstra 的改進版, Dijkstra 只利用到中間的點到起始點的距離這個訊息, 去選擇最短的節點加入標記集, 而A* 則改進為, 增加中間的點到終點的距離估計值, 用這兩個訊息去設計演算法, 會使效能更高.

註: 還有一個 all pairs shortest path 的演算法叫 Johnson

註:
無限大 ∞ \infty , 在 Python 中, 可以用 math.infsys.maxsize

import math
Infinity = math.inf

import sys
Infinity = sys.maxsize

Michael Sambol 的 ∞ \infty Python表法:

infinity = float("inf")

“Talk is cheap. Show me the code.”
― Linus Torvalds

老子第41章
上德若谷
大白若辱
大方無隅
大器晚成
大音希聲
大象無形
道隱無名

拳打千遍, 身法自然

“There’s no shortage of remarkable ideas, what’s missing is the will to execute them.” – Seth Godin
「很棒的點子永遠不會匱乏,然而缺少的是執行點子的意志力。」—賽斯.高汀


致謝: 非常感謝製作 Python turtle demo 的義工群!

本系列文章之連結

  • 從turtle海龜動畫學習Python-高中彈性課程1 link

  • 從turtle海龜動畫 學習 Python - 高中彈性課程系列 2 安裝 Python, 線上執行 Python link

  • 從turtle海龜動畫 學習 Python - 高中彈性課程系列 3 烏龜繪圖 所需之Python基礎 link

  • 從turtle海龜動畫 學習 Python - 高中彈性課程系列 4 烏龜開始畫圖 link

  • 從turtle海龜動畫 學習 Python - 高中彈性課程系列 5 用函數封裝重複性指令-呼叫函數令烏龜畫正 n 邊形 link

  • 從turtle海龜動畫 學習 Python - 高中彈性課程系列 6 畫多重旋轉圓,螺旋正方形 link

  • 從turtle海龜動畫 學習 Python - 7 遞歸 recursive - 高中彈性課程系列 link

  • 從turtle海龜動畫 學習 Python - 高中彈性課程系列 8 碎形 (分形 fractal) link

  • 從turtle海龜動畫 學習 Python - 高中彈性課程系列 8.1 碎形 L-system link

  • 從turtle海龜動畫 學習 Python - 高中彈性課程系列 9 Python 物件導向介紹 link

  • 從turtle海龜動畫 學習 Python - 高中彈性課程系列 9.1 Python 物件導向的練習 link

  • 從turtle海龜動畫 學習 Python - 高中彈性課程系列 10 藝術畫 自定義海龜形狀 link

  • 從turtle海龜動畫 學習 Python - 高中彈性課程系列 10.1 藝術畫 python繪製天然雪花結晶 https://blog.csdn.net/m0_47985483/article/details/122262036 link

  • 從turtle海龜動畫 學習 Python - 高中彈性課程系列 10.2 藝術畫 Python 製作生成式藝術 link

  • 從turtle海龜動畫 學習 Python - 高中彈性課程系列 11.1 氣泡排序 - 用 turtle 呈現演算法之執行動作 link

  • 從turtle海龜動畫 學習 Python - 高中彈性課程系列 11.2 maze 迷宮 - 用 turtle 呈現演算法之執行動作 link

  • 從turtle海龜動畫 學習 Python - 高中彈性課程系列 11.3 連分數演算法與轉轉相除法- 用 turtle 呈現演算法之執行動作 link

  • 從turtle海龜動畫 學習 Python - 高中彈性課程系列 11.4 最短路徑 Dijkstra- 用 turtle 呈現演算法之執行動作 link


Dijkstra algorithm 的流程

一般是將此算法歸類為貪心法範疇,
持續地將目前最短的節點收入標記集, 稱為標記點,
以此新收入標記集之標記節點對其他節點(只與此新標記節點相鄰之所有節點) 做距離更新,
重複做, 到所有的節點均被收入標記集, 就結束.

weight = 記錄各邊權重之矩陣
(目前暫時之)最短距離集 D
標記集 Y

粗略大綱: 其實最主要的只有兩步驟( 1 求最小節點, 2 更新):

  1. 初始化: 起始節點的 D 值為零, 其餘節點的 D 值為 ∞ \infty ,
    D = [ 0 , ∞ , ∞ , ∞ , ⋯   , ∞ ] D = [0, \infty, \infty, \infty , \cdots, \infty ] D=[0,,,,,]

  2. 求最小距離點: 將標記節之外的所有節點, 取距離最小(D值最小)的那個, 加入標記集.

  3. 更新距離(有的書稱為"鬆弛" relaxing): 只與新標記節點相鄰之所有節點, 做更新動作:
    例如, 假設 B 剛加入Y, 而 C 與 B 相鄰, 要鬆弛 B → C B \rightarrow C BC
    則估算
    D ( B ) + w e i g t h ( B , C ) < ? D ( C ) D(B) + weigth(B,C)\overset{?}{<} D(C) D(B)+weigth(B,C)<?D(C)
    若成立,
    則將 D(C) 更新為 D(B) + weigth(B,C).

Dijkstra algorithm 的關鍵步驟:

  • 只針對還在最小節點集 (標記集, 最短距離集) 之外的點, 找 D[k] 最小的, 收入最小節點集, 成為新的最小節點
  • 只針對最新(最近)剛收入最小節點集的最小節點, 有跟他相連的還在之外的點, 對距離做鬆弛更新,
    例如, 令最p為新收入最小節點集的最小節點, k 為還在最小節點集之外的點,
    檢查, if D[p]+weight[p][k] < D[k],
  • k 經由 p 到 start point 的距離若比當時本身註記的最短的距離 D[k]小的話,便會執行更新 D[k] 作業
  • 一直到最小節點集就是整體集合, 就停止.

Dijkstra 原始論文的 流程敘述

dijkstra原始論文_流程敘述

Wiki 上的 虛擬碼 pseudocodes

Wiki 的虛擬碼,
Wiki 虛擬碼 的 Q 是 標記集 Y 的補集, i.e., Q = Y c Q = Y^{c} Q=Yc, or, Q = V − Y Q = V-Y Q=VY
dist[] 就是 暫時最短距離集 D[]
prev[v] 記錄 v的 最短路徑中 v 的前一個節點
Graph.Edges(u, v) 就是邊的權重(距離)

較簡版本, 每次重新找最小值, 沒使用優先隊列

1  function Dijkstra(Graph, source):
 2      
 3      for each vertex v in Graph.Vertices:
 4          dist[v] ← INFINITY
 5          prev[v] ← UNDEFINED
 6          add v to Q
 7      dist[source] ← 0
 8      
 9      while Q is not empty:
10          u ← vertex in Q with min dist[u]
11          remove u from Q
12          
13          for each neighbor v of u still in Q:
14              alt ← dist[u] + Graph.Edges(u, v)
15              if alt < dist[v]:
16                  dist[v] ← alt
17                  prev[v] ← u
18
19      return dist[], prev[]

使用 優先隊列版本

對於在標記集 Y 之外的點, Q = V − Y Q = V-Y Q=VY, 可以先以優先隊列(優先樹)將它們排好順序, 順序參考的值(value) 是用 D 的值, i.e., 暫時最短距離當參考值.

優先隊列有3 個基本操作:
A min-priority queue is an abstract data type that provides 3 basic operations: add_with_priority(), decrease_priority() and extract_min().


#  using min-priority queue

1  function Dijkstra(Graph, source):
2      dist[source] ← 0                           // Initialization
3
4      create vertex priority queue Q
5
6      for each vertex v in Graph.Vertices:
7          if v ≠ source
8              dist[v] ← INFINITY                 // Unknown distance from source to v
9              prev[v] ← UNDEFINED                // Predecessor of v
10
11         Q.add_with_priority(v, dist[v])
12
13
14     while Q is not empty:                      // The main loop
15         u ← Q.extract_min()                    // Remove and return best vertex
16         for each neighbor v of u:              // Go through all v neighbors of u
17             alt ← dist[u] + Graph.Edges(u, v)
18             if alt < dist[v]:
19                 dist[v] ← alt
20                 prev[v] ← u
21                 Q.decrease_priority(v, alt)
22
23     return dist, prev

Sambol 的 Dijkstra 虛擬碼 pseudocodes

Sambo 的 Dijkstra 虛擬碼 pseudocodes 與 Wiki 上的 Dijkstra 使用 優先隊列版本虛擬碼 類似.
Sambo 的 Dijkstra  虛擬碼 pseudocodes_Dijkstra's algorithm in 3 minutes
Ref: Michael Sambol, Dijkstra’s algorithm in 3 minutes, https://youtu.be/_lHSawdgXpI link

Dijkstra algorithm 的例子示範

效能

Dijkstra 效能為 O(N^2)
或是 用最小堆積 minmal heap, 可以加速為 O((N+M)*log(N)

(Bellman-Ford 的效能略差, 是 O(M*N) , M 為邊數, N 頂點數.)

Dijkstra algorithm 的 Python codes 實作

以下是博主改寫自 C 語言的版本
河西朝雄: 最短路徑問題(戴克斯特拉法)7-5 Dijkstra, Rei55.c
Rei55_河西朝雄_7_5_Dijkstra_改成Neapolitan符號.py

河西朝雄C程式碼對照之圖例
河西朝雄Dr55_Dijkstra圖例

此河西朝雄的內容較精簡, 可以抓住中心想法, 不至於被一些物件導向或是圖的結構等語法分散注意力.

##//Ref: 河西朝雄 最短路徑問題(戴克斯特拉法)7-5 Dijkstra, Rei55.c
##//By Prof. P-J Lai MATH NKNU 20210330
## Rei55_河西朝雄_7_5_Dijkstra_改成Neapolitan符號.py
# 20230606 revised

# algo 的關鍵步驟:
# 1. 只針對還在最小節點集之外的點, 找leng[k]最小的, 收入最小節點集, 成為新的最小節點
# 2. 只針對最新收入最小節點集的最小節點, 有跟他相連的還在之外的點, 對距離做鬆弛更新,
# 例如, 令最p為新收入最小節點集的最小節點, k 為還在最小節點集之外的點,
# 檢查, if leng[p]+a[p][k] < leng[k],
# k 經由 p 到 start point 的距離若比當時本身註記的最短的距離leng[k]小的話,便會執行更新leng[k]作業
# 一直到最小節點集就是整體集合, 就停止.


#https://ithelp.ithome.com.tw/articles/10209593
##其基本原理是:每次新擴展一個距離最短的點,更新與其相鄰的點的距離。
##當所有邊權都為正時,由於不會存在一個距離更短的沒擴展過的點,所以這個點
##的距離永遠不會再被改變,因而保證了演算法的正確性。
##不過根據這個原理,用Dijkstra求最短路的圖不能有負權邊,因為擴展到負權邊的時候會產生更短
##的距離,有可能就破壞了已經更新的點距離不會改變的性質。


#############################################################

import math
N = 8         #/* 節點數量 */
M = math.inf

#/* 相鄰矩陣 */
weight =[             [0,0,0,0,0,0,0,0,0], 
                 [0,0,1,7,2,M,M,M,M],
                 [0,1,0,M,M,2,4,M,M],
                 [0,7,M,0,M,M,2,3,M],
                 [0,2,M,M,0,M,M,5,M],
                 [0,M,2,M,M,0,1,M,M],
                 [0,M,4,2,M,1,0,M,6],
                 [0,M,M,3,5,M,M,0,2],
                 [0,M,M,M,M,M,6,2,0]    ]

# 河西朝雄原來的符號與變數
# int j,k,p,start,min,
#     leng[N+1],            /* 至起始節點的距離 */
#leng = [0 for i in range(0, N+1)]
#     flagVisit[N+1];       /* 是否已收最小節點集的旗標 */
#visitFlag = [0 for i in range(0, N+1)]
# printf("起點: ");scanf("%d",&start);

# 改成Neapolitan符號
# int j,k,p,start,min,
#     D[N+1]=[0, M, M,,,M]      /* 至起始節點的暫時最短距離 */
#     Y[N+1]=[1,0,,,,0]         /* 是否已收入最小節點集(標記集) */
# newst_tag_Vertex 最新的最小節點(標記點)
D = [M for i in range(0, N+1)]
Y = [0 for i in range(0, N+1)]
start = int(input("起點: "))

D[start]=0

for j in range(1, N+1):
    min = M

    # Finding current minimal distance vertex to tag
    #/* 搜尋最小的節點 */
    # 只針對還在最小節點集之外的點, 找leng[k]最小的, 收入最小節點集, 成為新的最小節點
    for k in range(1, N+1):
        if Y[k]==0 and D[k]<min:
            newst_tag_Vertex=k 
            min=D[k]
        
    #/* 確定 newst_tag_Vertex 為最小的節點, 收入最小節點集, 將Y[newst_tag_Vertex]設為1 
    Y[newst_tag_Vertex]=1            

    if min==M:
        print("圖形沒有連接\n")
        break
    

    #/* 經由p至k的距離若比當時最短的距離小的話,便會執行更新作業 */
    # 只針對最新收入最小節點集的最小節點(目前為 newst_tag_Vertex),
    # 有跟他相連的, 還在最小節點集之外的點, 對距離做鬆弛更新
    # relaxation
    for k in range(1, N+1):
        if D[newst_tag_Vertex]+weight[newst_tag_Vertex][k] < D[k]:
            D[k]=D[newst_tag_Vertex]+weight[newst_tag_Vertex][k]
    
# print shortest paths lengths: from start to all other vertices
print("print shortest paths lengths: from start to all other vertices")
for j in range(1, N+1):
    print(f"{start} -> {j} : {D[j]}\n")


##執行結果
##>>> 
##= RESTART: D:\NEW_筆電的\網路免費軟體資料\Python教學\Python演算法\圖的演算法_Dijkstra_Prim等\最短路徑_Dijkstra's_Algorithm\Rei55_河西朝雄_7_5_Dijkstra_改成Neapolitan符號.py
##起點: 1
##print shortest paths lengths: from start to all other vertices
##1 -> 1 : 0
##
##1 -> 2 : 1
##
##1 -> 3 : 6
##
##1 -> 4 : 2
##
##1 -> 5 : 3
##
##1 -> 6 : 4
##
##1 -> 7 : 7
##
##1 -> 8 : 9



##########################################################

##//河西朝雄 7-5 Dijkstra
##//用 dev C++, 用 C語言的project, 而不是C++的project.
##//By Prof. P-J Lai MATH NKNU 20141103 
##
##
##Rei55.c
##此程式把起點, 例如1, 到每個節點之最短路徑長印出
##並不會印出路徑的走法
##注意: C的程式不需要 return 0
##/*
## * ---------------------------------------
## * 最短路徑問題(戴克斯特拉法)   *
## * ---------------------------------------
## */
##
##
###include <stdio.h>
##
###define N 8         /* ¸`ÂI¼Æ¶q */
###define M 9999
##
##int a[N+1][N+1]={{0,0,0,0,0,0,0,0,0}, /* ¬Û¾F¯x°} */
##                 {0,0,1,7,2,M,M,M,M},
##                 {0,1,0,M,M,2,4,M,M},
##                 {0,7,M,0,M,M,2,3,M},
##                 {0,2,M,M,0,M,M,5,M},
##                 {0,M,2,M,M,0,1,M,M},
##                 {0,M,4,2,M,1,0,M,6},
##                 {0,M,M,3,5,M,M,0,2},
##                 {0,M,M,M,M,M,6,2,0}};
##int main(void)
##{
##    int j,k,p,start,min,
##        leng[N+1],              /* ¦Ü¸`ÂIªº¶ZÂ÷ */
##        v[N+1];                 /* ½T©wºX¼Ð */
##
##    printf("°_ÂI: ");scanf("%d",&start);
##    for (k=1;k<=N;k++){
##        leng[k]=M;v[k]=0;
##    }
##    leng[start]=0;
##
##    for (j=1;j<=N;j++){
##        min=M;          /* ·j´M³Ì¤pªº¸`ÂI */
##        for (k=1;k<=N;k++){
##            if (v[k]==0 && leng[k]<min){
##                p=k; min=leng[k];
##            }
##        }
##        v[p]=1;            /* ½T©w³Ì¤pªº¸`ÂI */
##
##        if (min==M){
##            printf("¹Ï§Î¨S¦³³s±µ\n");
##            return 1;
##        }
##
##        /* ¸g¥Ñp¦Ükªº¶ZÂ÷­Y¤ñ·í®É³Ìµuªº¶ZÂ÷¤pªº¸Ü¡A«K·|°õ¦æ§ó·s§@·~ */
##        for (k=1;k<=N;k++){
##            if((leng[p]+a[p][k])<leng[k])
##                leng[k]=leng[p]+a[p][k];
##        }
##    }
##    for (j=1;j<=N;j++)
##        printf("%d -> %d : %d\n",start,j,leng[j]);
##}
                                                                                                                                                                                                                                                                                                                        

執行結果

>>> 
= RESTART: D:/NEW_筆電的/網路免費軟體資料/Python教學/Python演算法/圖的演算法_Dijkstra_Prim等/最短路徑_Dijkstra's_Algorithm/Rei55_河西朝雄_7_5_Dijkstra_改成Neapolitan符號.py
起點: 1
1 -> 1 : 0

1 -> 2 : 1

1 -> 3 : 6

1 -> 4 : 2

1 -> 5 : 3

1 -> 6 : 4

1 -> 7 : 7

1 -> 8 : 9


>>> 
= RESTART: D:\NEW_筆電的\網路免費軟體資料\Python教學\Python演算法\圖的演算法_Dijkstra_Prim等\最短路徑_Dijkstra's_Algorithm\Rei55_河西朝雄_7_5_Dijkstra_改成Neapolitan符號.py
起點: 2
print shortest paths lengths: from start to all other vertices
2 -> 1 : 1

2 -> 2 : 0

2 -> 3 : 5

2 -> 4 : 3

2 -> 5 : 2

2 -> 6 : 3

2 -> 7 : 8

2 -> 8 : 9

Ex1 改寫成可以輸出路徑經過之城市

##//Ref: 河西朝雄 最短路徑問題(戴克斯特拉法)7-5 Dijkstra, Dr55.c
##//By Prof. P-J Lai MATH NKNU 20230606
## Dr55_河西朝雄_7_5_Dijkstra_印出路徑.py
# 以上會輸出最短路徑經過之城市編號
# 20230606 revised

#############################################################

import math
N = 8         #/* 節點數量 */
M = math.inf

#/* 相鄰矩陣 */
weight =[             [0,0,0,0,0,0,0,0,0], 
                 [0,0,1,7,2,M,M,M,M],
                 [0,1,0,M,M,2,4,M,M],
                 [0,7,M,0,M,M,2,3,M],
                 [0,2,M,M,0,M,M,5,M],
                 [0,M,2,M,M,0,1,M,M],
                 [0,M,4,2,M,1,0,M,6],
                 [0,M,M,3,5,M,M,0,2],
                 [0,M,M,M,M,M,6,2,0]    ]


# 改成Neapolitan符號
# int j,k,p,start,min,
#     D[N+1]=[0, M, M,,,M]      /* 至起始節點的暫時最短距離 */
#     Y[N+1]=[1,0,,,,0]         /* 是否已收入最小節點集(標記集) */
# newst_tag_Vertex 最新的最小節點(標記點)
D = [M for i in range(0, N+1)]
Y = [0 for i in range(0, N+1)]
start = int(input("起點: "))

predecessor = [0 for i in range(0, N+1)]


D[start]=0
# predecessor[i] = 0, 指 i 的父節點為空
predecessor[start] = 0

for j in range(1, N+1):
    min = M

    # Finding current minimal distance vertex to tag
    #/* 搜尋最小的節點 */
    # 只針對還在最小節點集之外的點, 找leng[k]最小的, 收入最小節點集, 成為新的最小節點
    for k in range(1, N+1):
        if Y[k]==0 and D[k]<min:
            newst_tag_Vertex=k 
            min=D[k]
        
    #/* 確定 newst_tag_Vertex 為最小的節點, 收入最小節點集, 將Y[newst_tag_Vertex]設為1 
    Y[newst_tag_Vertex]=1               

    if min==M:
        print("圖形沒有連接\n")
        break
    

    #/* 經由p至k的距離若比當時最短的距離小的話,便會執行更新作業 */
    # 只針對最新收入最小節點集的最小節點(目前為 newst_tag_Vertex),
    # 有跟他相連的, 還在最小節點集之外的點, 對距離做鬆弛更新
    # relaxation
    for k in range(1, N+1):
        if D[newst_tag_Vertex]+weight[newst_tag_Vertex][k] < D[k]:
            D[k]=D[newst_tag_Vertex]+weight[newst_tag_Vertex][k]
            predecessor[k] = newst_tag_Vertex
    
for j in range(1, N+1):
    print(predecessor[j], end=", ")

print("\n")    
    

for j in range(1, N+1):
    print(f"最短路徑長度從 城市{start} ->到城市{j}{D[j]}\n")
    #print(f"{start} -> {j} : {D[j]}\n")

    print("輸出最短路徑經過之城市編號")
    print(f"the shortest path of {start} -> {j} is")
    
    if predecessor[j] != 0:
            
        current = j
        before = predecessor[j]
        
        for i in  range(1, N+1):
            while before != 0:
                #if before != 0 and before != start:
                print(f"{current} <- {before}", end=", ")
                current = before
                before = predecessor[current]
         
    else:
        print(f"{j} <- {start}", end="")

    print("\n")

執行結果

>>> 
= RESTART: D:\NEW_筆電的\網路免費軟體資料\Python教學\Python演算法\圖的演算法_Dijkstra_Prim等\最短路徑_Dijkstra's_Algorithm\Dr55_河西朝雄_7_5_Dijkstra_印出路徑.py
起點: 2
2, 0, 6, 1, 2, 5, 4, 6, 

最短路徑長度從 城市2 ->到城市11

輸出最短路徑經過之城市編號
the shortest path of 2 -> 1 is
1 <- 2, 

最短路徑長度從 城市2 ->到城市20

輸出最短路徑經過之城市編號
the shortest path of 2 -> 2 is
2 <- 2

最短路徑長度從 城市2 ->到城市35

輸出最短路徑經過之城市編號
the shortest path of 2 -> 3 is
3 <- 6, 6 <- 5, 5 <- 2, 

最短路徑長度從 城市2 ->到城市43

輸出最短路徑經過之城市編號
the shortest path of 2 -> 4 is
4 <- 1, 1 <- 2, 

最短路徑長度從 城市2 ->到城市52

輸出最短路徑經過之城市編號
the shortest path of 2 -> 5 is
5 <- 2, 

最短路徑長度從 城市2 ->到城市63

輸出最短路徑經過之城市編號
the shortest path of 2 -> 6 is
6 <- 5, 5 <- 2, 

最短路徑長度從 城市2 ->到城市78

輸出最短路徑經過之城市編號
the shortest path of 2 -> 7 is
7 <- 4, 4 <- 1, 1 <- 2, 

最短路徑長度從 城市2 ->到城市89

輸出最短路徑經過之城市編號
the shortest path of 2 -> 8 is
8 <- 6, 6 <- 5, 5 <- 2, 

Ex2 以上之輸出, 稍嫌凌亂, 同學可以改善輸出之呈現嗎?

Ans:
以下是博主嘗試的一個改善輸出呈現的版本:

##//Ref: 河西朝雄 最短路徑問題(戴克斯特拉法)7-5 Dijkstra, Dr55.c
##//By Prof. P-J Lai MATH NKNU 20230606
## Dr55_河西朝雄_7_5_Dijkstra_印出路徑_改善輸出.py
# 用 list.pop(), 就會從後往前印.
# 20230620 revised

# algo 的關鍵步驟:
# 1. 只針對還在最小節點集之外的點, 找leng[k]最小的, 收入最小節點集, 成為新的最小節點
# 2. 只針對最新收入最小節點集的最小節點, 有跟他相連的還在之外的點, 對距離做鬆弛更新,
# 例如, 令最p為新收入最小節點集的最小節點, k 為還在最小節點集之外的點,
# 檢查, if leng[p]+a[p][k] < leng[k],
# k 經由 p 到 start point 的距離若比當時本身註記的最短的距離leng[k]小的話,便會執行更新leng[k]作業
# 一直到最小節點集就是整體集合, 就停止.

#############################################################

import math
N = 8         #/* 節點數量 */
M = math.inf

#/* 相鄰矩陣 */
weight =[             [0,0,0,0,0,0,0,0,0], 
                 [0,0,1,7,2,M,M,M,M],
                 [0,1,0,M,M,2,4,M,M],
                 [0,7,M,0,M,M,2,3,M],
                 [0,2,M,M,0,M,M,5,M],
                 [0,M,2,M,M,0,1,M,M],
                 [0,M,4,2,M,1,0,M,6],
                 [0,M,M,3,5,M,M,0,2],
                 [0,M,M,M,M,M,6,2,0]    ]


# 改成Neapolitan符號
# int j,k,p,start,min,
#     D[N+1]=[0, M, M,,,M]      /* 至起始節點的暫時最短距離 */
#     Y[N+1]=[1,0,,,,0]         /* 是否已收入最小節點集(標記集) */
# newst_tag_Vertex 最新的最小節點(標記點)
D = [M for i in range(0, N+1)]
Y = [0 for i in range(0, N+1)]
start = int(input("起點: "))

predecessor = [0 for i in range(0, N+1)]


D[start]=0
# predecessor[i] = 0, 指 i 的父節點為空
predecessor[start] = 0

for j in range(1, N+1):
    min = M

    # Finding current minimal distance vertex to tag
    #/* 搜尋最小的節點 */
    # 只針對還在最小節點集之外的點, 找leng[k]最小的, 收入最小節點集, 成為新的最小節點
    for k in range(1, N+1):
        if Y[k]==0 and D[k]<min:
            newst_tag_Vertex=k 
            min=D[k]
        
    #/* 確定 newst_tag_Vertex 為最小的節點, 收入最小節點集, 將Y[newst_tag_Vertex]設為1 
    Y[newst_tag_Vertex]=1               

    if min==M:
        print("圖形沒有連接\n")
        break
    

    #/* 經由p至k的距離若比當時最短的距離小的話,便會執行更新作業 */
    # 只針對最新收入最小節點集的最小節點(目前為 newst_tag_Vertex),
    # 有跟他相連的, 還在最小節點集之外的點, 對距離做鬆弛更新
    # relaxation
    for k in range(1, N+1):
        if D[newst_tag_Vertex]+weight[newst_tag_Vertex][k] < D[k]:
            D[k]=D[newst_tag_Vertex]+weight[newst_tag_Vertex][k]
            predecessor[k] = newst_tag_Vertex

print("\n")    
    
for j in range(1, N+1):
    print(f"最短路徑長度從 城市{start} -> 到城市{j}{D[j]}")
    #print(f"{start} -> {j} : {D[j]}\n")

    print("經過之城市編號", end=" ")
    print(f"the shortest path of {start} -> {j} is")

    stack = []
    stack.append(j)
    
    if predecessor[j] != 0:
            
        current = j
        previous = predecessor[j]
        
        
        for i in  range(1, N+1):
            while previous != 0:
                stack.append(previous)
                #if before != 0 and before != start:
                #print(f"{current} <- {previous}", end=", ")
                current = previous
                previous = predecessor[current]
        #print(len(stack))
        for k in range(len(stack)-1):
            print(f"{stack.pop()} ->", end=" ")
        print(f"{stack.pop()}.", end=" ")   
         
    else:
        #print(f"{j} <- {start}", end="")
        print(f"{start} -> {j}", end="")

    print("\n")

執行結果

>>> 
= RESTART: E:\NEW_筆電的\網路免費軟體資料\Python教學\Python演算法\圖的演算法_Dijkstra_Prim等\最短路徑_Dijkstra's_Algorithm\Dr55_河西朝雄_7_5_Dijkstra_印出路徑_改善輸出.py
起點: 1

最短路徑長度從 城市1 -> 到城市10
經過之城市編號 the shortest path of 1 -> 1 is
1 -> 1

最短路徑長度從 城市1 -> 到城市21
經過之城市編號 the shortest path of 1 -> 2 is
1 -> 2. 

最短路徑長度從 城市1 -> 到城市36
經過之城市編號 the shortest path of 1 -> 3 is
1 -> 2 -> 5 -> 6 -> 3. 

最短路徑長度從 城市1 -> 到城市42
經過之城市編號 the shortest path of 1 -> 4 is
1 -> 4. 

最短路徑長度從 城市1 -> 到城市53
經過之城市編號 the shortest path of 1 -> 5 is
1 -> 2 -> 5. 

最短路徑長度從 城市1 -> 到城市64
經過之城市編號 the shortest path of 1 -> 6 is
1 -> 2 -> 5 -> 6. 

最短路徑長度從 城市1 -> 到城市77
經過之城市編號 the shortest path of 1 -> 7 is
1 -> 4 -> 7. 

最短路徑長度從 城市1 -> 到城市89
經過之城市編號 the shortest path of 1 -> 8 is
1 -> 4 -> 7 -> 8. 
>>> 
= RESTART: E:\NEW_筆電的\網路免費軟體資料\Python教學\Python演算法\圖的演算法_Dijkstra_Prim等\最短路徑_Dijkstra's_Algorithm\Dr55_河西朝雄_7_5_Dijkstra_印出路徑_改善輸出.py
起點: 5

最短路徑長度從 城市5 -> 到城市13
經過之城市編號 the shortest path of 5 -> 1 is
5 -> 2 -> 1. 

最短路徑長度從 城市5 -> 到城市22
經過之城市編號 the shortest path of 5 -> 2 is
5 -> 2. 

最短路徑長度從 城市5 -> 到城市33
經過之城市編號 the shortest path of 5 -> 3 is
5 -> 6 -> 3. 

最短路徑長度從 城市5 -> 到城市45
經過之城市編號 the shortest path of 5 -> 4 is
5 -> 2 -> 1 -> 4. 

最短路徑長度從 城市5 -> 到城市50
經過之城市編號 the shortest path of 5 -> 5 is
5 -> 5

最短路徑長度從 城市5 -> 到城市61
經過之城市編號 the shortest path of 5 -> 6 is
5 -> 6. 

最短路徑長度從 城市5 -> 到城市76
經過之城市編號 the shortest path of 5 -> 7 is
5 -> 6 -> 3 -> 7. 

最短路徑長度從 城市5 -> 到城市87
經過之城市編號 the shortest path of 5 -> 8 is
5 -> 6 -> 8. 

Bellman-Ford algorithm

Bellman-Ford 的功效與 Dijkstra 一樣, 都是解決單源最短路徑問題,
只差別在, Bellman-Ford 的圖可以有路徑長(權重)是負的,
但是不允許圖中有負權重的迴路.

他的效能略差, 是 O(M*N) , M 為邊數, N 頂點數.
(Dijkstra 效能為 O(N^2)
或是 用最小堆積 minmal heap, 可以加速為 O((N+M)*log(N) )

根據王碩這本書的提示:

需指定單一來源點,
對所有的邊進行 n-1 次鬆弛,
n = number(vertices) n 為頂點數,
鬆弛順序隨意, 可以一直用同樣順序,
如果連續兩次得到同樣的結果, 可以提前結束.

鬆弛的方法跟 Dijkstra 一樣
例如 要鬆弛 B → C B \rightarrow C BC
則估算
D ( B ) + w e i g h t ( B , C ) < ? D ( C ) D(B) + weight(B,C)\overset{?}{<} D(C) D(B)+weight(B,C)<?D(C)
若成立,
則將 D(C) 更新為 D(B) + weight(B,C)`.


Ref: 渡部有隆, 會動的演算法,_https://www.flag.com.tw/activity/F2708/exercise/books/ link

根據 渡部有隆: 會動的演算法, 這本, Bellman-Ford 的 流程與 王碩的一樣, 都是持續更新每個邊, 只是他的圖解, 是以頂點為輪流次序, 對每個取出的頂點, 對與這個頂點相鄰的邊做鬆弛, 這種說法跟依序更新每個邊是一樣的.

Bellman-Ford algorithm 的 pseudocode

Bellman-Ford algorithm 的 pseudocode
Ref: Michael Sambol,
Bellman-Ford in 5 minutes — Step by step example, https://youtu.be/obWXjtg0L64.

Bellman-Ford algorithm 的例子示範

Bellman-Ford algorithmm 的 Python codes

以下是 王碩那本書的 Python codes:

# Test by Prof. P-J Lai MATH NKNU 20210404
#Ref: 王碩ch13.2_BellmanFord.py
# Dijkstra 要求權重不為負, Bellman-Ford則不要求
# Bellman-Ford 要求不存在負權值迴路

# Bellman-Ford 演算法
# 1. 選擇 source, 設其 dist=0, 其餘nodes 之 dist = sys.maxsize
# 2. 依次鬆弛所有邊
# 3. 重複 2. 達 n-2 次, n 為 nodes 數量, 直到連兩次鬆弛都得到同樣結果 
# 複雜度 O(|v|*|E|), |v|=number of nodes, |E|=number of edges 


# 以下王碩程式碼, 是以 edges =[ ['a','b',2, False],['a','c',0, True],,,]
# 造成 coeds較難讀, 可以改用頂點與距離分開的方式存放
# Ex: 改用頂點, 距離, 是否雙向, 分開的方式存放, 重構一下codes
# 看 codes 是否因此較清晰易讀?

import sys

class Graph():

    def __init__(self):
        self.vertices = {}
        self.edges = []
        
    def addEdge(self, start, end, dist, biDirectFlag = True):
        if biDirectFlag:
            self.edges.extend([[start, end, dist], [end, start, dist]])
        else:
            self.edges.append([start, end, dist])

    def getVertices(self):
        return set(sum( ( [edge[0], edge[1]] for edge in self.edges), [] ) )

## Python_sum_打平嵌套列表_Python中的sum函数一个妙用
##    >>> sum( [['a','b'],['b','c']], [])
##        ['a', 'b', 'b', 'c']
##    >>> set(sum( [['a','b'],['b','c']], []))
##        {'b', 'a', 'c'}
    def printSolution(self, dist, predecessor):
        for v in self.vertices:
            path = self.getPath(predecessor, v)
            print(self.src, "to", v, " - Distance:", dist[v], "Path - :", path)

    def getPath(self, predecessor, v):
        pred = predecessor[v]
        path =[]
        path.append(v)
        while pred!=None:
            path.append(pred)
            pred = predecessor[pred]

        path.reverse()
        return(path)

    def bellmanFord(self, src):
        self.src = src
        self.vertices = self.getVertices()

        dist = {v: sys.maxsize for v in self.vertices}
        dist[src] = 0
        predecessor = {v: None for v in self.vertices}

        for i in range(len(self.vertices)-1):
            for edge in self.edges:
                if dist[edge[0]] + edge[2] < dist[edge[1]]:
                        dist[edge[1]] = dist[edge[0]] + edge[2]
                        predecessor[edge[1]] = edge[0]

        self.printSolution(dist, predecessor)

#################################################
# 執行批次指令

graph = Graph()
graph.addEdge('a','b',2, False)
graph.addEdge('a','c',0, True)
graph.addEdge('c','b',-2, False)
graph.addEdge('b','d',-3, False)
graph.addEdge('c','e',4, False)
graph.addEdge('d','e',-1, False)
graph.addEdge('d','f',1, True)
graph.addEdge('e','f',2, False)

graph.bellmanFord('a')

執行結果

>>> 
================= RESTART: C:/Users/user/Desktop/王碩ch13.2_BellmanFord.py ================
a to c  - Distance: 0 Path - : ['a', 'c']
a to d  - Distance: -5 Path - : ['a', 'c', 'b', 'd']
a to e  - Distance: -6 Path - : ['a', 'c', 'b', 'd', 'e']
a to f  - Distance: -4 Path - : ['a', 'c', 'b', 'd', 'f']
a to a  - Distance: 0 Path - : ['a']
a to b  - Distance: -2 Path - : ['a', 'c', 'b']                 
        

以下是 Michael Sambol 的 Python codes:

Ref: Michael Sambol 的 Python codes, https://github.com/msambol/youtube/tree/master/shortest_path link

# Ref Michael Sambol 的 Python codes, https://github.com/msambol/youtube/tree/master/shortest_path

infinity = float("inf")

def make_graph():
    # identical graph as the YouTube video: https://youtu.be/obWXjtg0L64
    # tuple = (cost, to_node)
    return {
        'S': [(8, 'E'), (10, 'A')],
        'A': [(2, 'C')],
        'B': [(1, 'A')],
        'C': [(-2, 'B')],
        'D': [(-4, 'A'), (-1, 'C')],
        'E': [(1, 'D')],
    }


def make_graph_with_negative_cycle():
    return {
        'S': [(8, 'E'), (10, 'A')],
        'A': [(-2, 'C')],
        'B': [(1, 'A')],
        'C': [(-2, 'B')],
        'D': [(-4, 'A'), (-1, 'C')],
        'E': [(1, 'D')],
    }


def bellman_ford(G, start):
    shortest_paths = {}
    
    for node in G:
        shortest_paths[node] = infinity

    shortest_paths[start] = 0
    size = len(G)

    for _ in range(size - 1):
        for node in G:
            for edge in G[node]:
                cost = edge[0]
                to_node = edge[1]
                if shortest_paths[node] + cost < shortest_paths[to_node]:
                    shortest_paths[to_node] = shortest_paths[node] + cost

    # iterate once more and check for negative cycle
    for node in G:
        for edge in G[node]:
            cost = edge[0]
            to_node = edge[1]
            if shortest_paths[node] + cost < shortest_paths[to_node]:
                return 'INVALID - negative cycle detected'

    return shortest_paths


def main():
    start = 'S'

    G = make_graph()
    shortest_paths = bellman_ford(G, start)
    print(f'Shortest path from {start}: {shortest_paths}')

    G = make_graph_with_negative_cycle()
    negative_cycle = bellman_ford(G, start)
    print(f'Shortest path from {start}: {negative_cycle}')

main()

##>>> 
##= RESTART: D:\NEW_筆電的\網路免費軟體資料\Python教學\Python演算法\圖的演算法_Dijkstra_Prim等\最短路徑_Bellman-Ford_Algorithm\bellman_ford_Sambo.py
##Shortest path from S: {'S': 0, 'A': 5, 'B': 5, 'C': 7, 'D': 9, 'E': 8}
##Shortest path from S: INVALID - negative cycle detected
##>>> 

執行結果

>>> 
= RESTART: D:\NEW_筆電的\網路免費軟體資料\Python教學\Python演算法\
圖的演算法_Dijkstra_Prim等\
最短路徑_Bellman-Ford_Algorithm\bellman_ford_Sambol.py
Shortest path from S: {'S': 0, 'A': 5, 'B': 5, 'C': 7, 'D': 9, 'E': 8}
Shortest path from S: INVALID - negative cycle detected

Floyd-Warshall algorithm

可以得出所有兩對頂點(節點 node)之間的最短路徑
解決可以輸出所有兩頂點對的最短路徑算法,
傳統 Floyd 是用動態規劃的觀點,


Cormen, Leiserson, Rivest, Stein, Introduction to algorithms.
有講到:
也可以重複使用 Bellman-Ford 或是 Dijkstra的方法, 對每個頂點, 指定他為起始點, 如此也可以得到輸出所有兩頂點對的最短路徑,
如果重複使用Dijkstra的方法, 使用最小優先佇列的線性陣列實作, 則時間是 O ( V 3 ) O(V^3) O(V3),
使用最小優先佇列的二元最小堆積實作, 則時間是
O ( V ⋅ E ⋅ l o g ( E ) O(V \cdot E \cdot log(E) O(VElog(E), 在圖形是稀疏時是一個改進.

如果有負權重, 只能重複使用 Bellman-Ford, 則時間是 O ( V 2 ⋅ E ) O(V^2\cdot E) O(V2E), 在一個密集圖形則是 O ( V 4 ) O(V^4) O(V4).

以下是使用這種方法的觀點,
是否可以與 Floyd 的原始動態規劃的做法整合,
或反過來以 Floyd 的方法改進 Bellman-Ford, 還待討論,

仔細看 Floyd-Warshall, , 是否,
就是將 Bellman-Ford 的方法推廣到任兩對節點之間的同樣計算,
例如, 假設 Bellman-Ford 是求起始點 v0 到其他點的最短距離,
所以 D list 是一維的, 紀錄 v0 ->v1, v0->v2,之目前之最短距離
Floyd-Warshall 將此 D[i] 推廣為 D[i][j] 二維的,
D[i][] 代表起始點 vi 到其他點的最短距離,
例如
D[0][k] 是一維的, 紀錄 v0 ->v1, v0->v2,之目前之最短距離
D[1][k] 是一維的, 紀錄 v1 ->v0, v1->v2,之目前之最短距離
等等,
即就是分別指定各個節點為單一來源, 每指定一個起始點 vi 的計算, 就是一個執行 Bellman-Ford 的 一維 D list 的過程,
所以 Floyd-Warshall 就是一個重複執行 Bellman-Ford 的 使用二維矩陣 D 的過程.

如果是的話,
Floyd-Warshall algorithm 的簡單流程說明
使用符號還幫助說明的話, 我們定義 Bellman-Ford(vi) 是一個求起始點 vi 到其他點的最短距離的動作,

則 重複使用 Bellman-Ford 的方法, 可以簡單表示為:

重複使用 Bellman-Ford algorithm
for all vertex v i ∈ V v_i \in V viV:
         \;\;\;\; Bellman-Ford( v i v_i vi)

所以 Floyd-Warshall 的 D 是一個二維矩陣,
更新或鬆弛的方法與 Dijkstra, Bellamn-Ford 是不一樣的 !
Dijkstra, Bellamn-Ford 每次會用到權重的原始矩陣,
但是 Floyd-Warshall, 原始的權重矩陣在第二輪就不需要了.
如果 Floyd-Warshall 與重複使用 Bellman-Ford 或是
Dijkstra 的方法可以整合,
讀者就會恍然大悟,
吾道一以貫之之感慨!

Floyd-Warshall algorithm:
參考王碩的說明:
指定任兩個節點, 比較以任何其他節點為中介點的距離,
這裡使用中介點的說法, 而不說是鬆弛每個邊,
主要在於, Floyd 的鬆弛是只使用 dist[][], 原始的權重矩陣在第二輪就不需要了.( dist[][] 初始化是權重矩陣, 之後就不斷鬆弛改進)
而 Bellman-Ford 則每次鬆弛都需要使用到 原始的權重矩陣.

(註: 用 Bellman-Ford 的說法, 則是鬆弛每個邊, 只是此時的D 值是針對此時的起點!
這裡引發博主問, 如果將 Bellman-Ford 改成與 Floyd 一樣的鬆弛更新方式, 是否可行?
也就是"只使用 dist[][], 原始的權重矩陣在第二輪就不需要".
),
如果有更小就更新距離,
D 矩陣為任兩個節點目前之最短距離,
P[i][j] 矩陣為i節點到j節點, i 經過的第一個節點.

註: Floyd 的 暫時最短距離矩陣 D[][], 似乎不指定起點, 不像 Dijkstra, Bellman-Ford 有一個起始點需要指定.

Floyd-Warshall algorithm 的 pseudocode

外層是中轉點(N減1次iteration),
中間是頂點之迴圈,
最內層是頂點 i 對每個其他頂點 j 的鬆弛 (透過中轉點k),

Floyd 使用的 dist 矩陣, 一開始是初始化為權重,
之後就不再使用權重矩陣了,
Floyd algorithm 的 pseudocode
Ref: Michael Sambol,
Floyd–Warshall algorithm in 4 minutes, https://youtu.be/4OQeCuLYj-4.

Floyd-Warshall algorithm 的例子示範

Floyd-Warshall algorithm 的 Python codes

以下是 王碩那本書的 Python codes:

# Test by Prof. P-J Lai MATH NKNU 20210405-0407
#Ref: 王碩ch13.3_Floyd.py
# Floyd 可以得到任兩點的最短路徑
# Dijkstra 要求權重不為負, Bellman-Ford 與 Floyd 則不要求
# Bellman-Ford 與 Floyd 要求不存在負權值迴路
# 複雜度 O(|v|^3), |v|=number of nodes 

# Floyd 演算法
##D(i,j)放 i->j 最短距離
##P(i,j) i->j 最短路徑經過的第一個節點(最接近i那個)
##D(i,j) 初始化為權重
##P(i,j) 初始化為 j
# 1. 創建 P 與 D 矩陣
# 2. 依次令所有節點為中介點, 比較任兩個節點通過中介點是否有更短, 如果有則更新矩陣
# 3. 由P可以得到任兩點的最短路線, D則提供 最短距離.

import sys
class Graph():
    def __init__(self):
        self.edges = []
        self.vertices = []

    def addEdge(self, start, end, dist, biDirectFlag = True):
        if biDirectFlag:
            self.edges.extend([[start, end, dist], [end, start, dist]])
        else:
            self.edges.append([start, end, dist])
        self.vertices = []
    def getVertices(self):
        vertices = list(set(sum( ( [edge[0], edge[1]] for edge in self.edges), [] ) ))
        return vertices

    def printSolution(self, DMatrix, PMatrix):
        n = len(DMatrix)
        for i in range(n):
            for j in range(n):
                print('From', self.vertices[i], 'to', self.vertices[j],
                      ' Distance:', DMatrix[i][j], ' Path:', self.getPath(i,j,PMatrix))

    def getPath(self, i, j, PMatrix):
        node = i
        path =[]
        while node!=j:
            path.append(self.vertices[node])
            node = PMatrix[node][j]
        path.append(self.vertices[j]) #加入最後一個j節點對應的名稱
        return path

    def getDMatrix(self):
        n = len(self.vertices)
        DMatrix = []
        for i in range(n):
            row = []
            for j in range(n):
                if i==j:
                    dist = 0
                else:
                    dist = sys.maxsize
                    # 
                    for edge in self.edges:
                        if self.vertices[i]==edge[0] and self.vertices[j]==edge[1]:
                            dist = edge[2]
                            break
                row.append(dist)
            DMatrix.append(row)
        return DMatrix

    def getPMatrix(self):
        n = len(self.vertices)
        PMatrix = []
        for i in range(n):
            row = []
            for j in range(n):
                row.append(j)
            PMatrix.append(row)
        return PMatrix

    def solveFloyd(self):
        self.vertices = self.getVertices()
        PMatrix = self.getPMatrix()
        DMatrix = self.getDMatrix()
        n = len(self.vertices)
        # k 是中介點
        for k in range(n):
            for i in range(n):
                for j in range(n):
                    if DMatrix[i][j] > DMatrix[i][k]+DMatrix[k][j]:
                        DMatrix[i][j] = DMatrix[i][k]+DMatrix[k][j]
                        PMatrix[i][j] = PMatrix[i][k]
        self.printSolution(DMatrix, PMatrix)
    
            
#################################################
# 執行批次指令

graph = Graph()
graph.addEdge('a','b',2, False)
graph.addEdge('a','c',0, True)
graph.addEdge('e','b',4, False)
graph.addEdge('b','c',3, False)
graph.addEdge('b','d',-3, False)
graph.addEdge('c','d',2, False)
graph.addEdge('c','e',4, False)
graph.addEdge('d','e',-1, False)
graph.addEdge('d','f',1, True)
graph.addEdge('e','f',2, False)

graph.solveFloyd()

執行結果

>>> 
= RESTART: C:\data\NEW\網路免費軟體資料\Python教學\Python演算法\最短路徑_Floyd_Algorithm\王碩ch13.3_Floyd.py
From c to c  Distance: 0  Path: ['c']
From c to b  Distance: 2  Path: ['c', 'a', 'b']
From c to e  Distance: -2  Path: ['c', 'a', 'b', 'd', 'e']
From c to f  Distance: 0  Path: ['c', 'a', 'b', 'd', 'f']
From c to a  Distance: 0  Path: ['c', 'a']
From c to d  Distance: -1  Path: ['c', 'a', 'b', 'd']
From b to c  Distance: 3  Path: ['b', 'c']
From b to b  Distance: 0  Path: ['b']
From b to e  Distance: -4  Path: ['b', 'd', 'e']
From b to f  Distance: -2  Path: ['b', 'd', 'f']
From b to a  Distance: 3  Path: ['b', 'c', 'a']
From b to d  Distance: -3  Path: ['b', 'd']
From e to c  Distance: 7  Path: ['e', 'b', 'c']
From e to b  Distance: 4  Path: ['e', 'b']
From e to e  Distance: 0  Path: ['e']
From e to f  Distance: 2  Path: ['e', 'f']
From e to a  Distance: 7  Path: ['e', 'b', 'c', 'a']
From e to d  Distance: 1  Path: ['e', 'b', 'd']
From f to c  Distance: 7  Path: ['f', 'd', 'e', 'b', 'c']
From f to b  Distance: 4  Path: ['f', 'd', 'e', 'b']
From f to e  Distance: 0  Path: ['f', 'd', 'e']
From f to f  Distance: 0  Path: ['f']
From f to a  Distance: 7  Path: ['f', 'd', 'e', 'b', 'c', 'a']
From f to d  Distance: 1  Path: ['f', 'd']
From a to c  Distance: 0  Path: ['a', 'c']
From a to b  Distance: 2  Path: ['a', 'b']
From a to e  Distance: -2  Path: ['a', 'b', 'd', 'e']
From a to f  Distance: 0  Path: ['a', 'b', 'd', 'f']
From a to a  Distance: 0  Path: ['a']
From a to d  Distance: -1  Path: ['a', 'b', 'd']
From d to c  Distance: 6  Path: ['d', 'e', 'b', 'c']
From d to b  Distance: 3  Path: ['d', 'e', 'b']
From d to e  Distance: -1  Path: ['d', 'e']
From d to f  Distance: 1  Path: ['d', 'f']
From d to a  Distance: 6  Path: ['d', 'e', 'b', 'c', 'a']
From d to d  Distance: 0  Path: ['d']
  

A* algorithm

A* algorithm 的例子示範

附錄

無限大 infinity , math.infsys.maxsize

在 Python 中, 可以用 math.infsys.maxsize

import math
Infinity = math.inf

import sys
Infinity = sys.maxsize

Michael Sambol 的 ∞ \infty Python表法:

infinity = float("inf")

何時用 Floyd-Warshall 或是 重複用 Dijkstra?

All pairs shortest-path problem 可以選擇使用 Floyd-Warshall 或是 重複用 Dijkstra, Bellman-Ford,
問題是甚麼情況較適合甚麼用法?
Wiki 有說明:

Ref: https://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm#Comparison_with_other_shortest_path_algorithms link

Comparison with other shortest path algorithms

The Floyd–Warshall algorithm is a good choice for computing paths between all pairs of vertices in dense
graphs, in which most or all pairs of vertices are connected by edges. For sparse graphs with non-negative
edge weights, a better choice is to use Dijkstra’s algorithm from each possible starting vertex, since the running time of repeated Dijkstra ( using Fibonacci heaps) is better than the
running time of the Floyd–Warshall algorithm when is significantly smaller than . For sparse graphs
with negative edges but no negative cycles, Johnson’s algorithm can be used, with the same asymptotic
running time as the repeated Dijkstra approach.
There are also known algorithms using fast matrix multiplication to speed up all-pairs shortest path
computation in dense graphs, but these typically make extra assumptions on the edge weights (such as
requiring them to be small integers).[15][16] In addition, because of the high constant factors in their running
time, they would only provide a speedup over the Floyd–Warshall algorithm for very large graphs.

四種方法對照表

四種方法對照表_Floyd Warshall All Pairs Shortest Path Algorithm  Graph Theory Dynamic Programming

Ref: WilliamFiset, Floyd Warshall All Pairs Shortest Path Algorithm Graph Theory Dynamic Programming, https://youtu.be/4NQ3HnhyNfQ link.

References

  • 一、v_JULY_v, A*搜索算法, csdn, 十五大经典算法研究
    专栏收录该内容 link

  • Dijkstra 原始論文:
    Dijkstra, E. W. A note on two problems in connexion with graphs (PDF). Numerische Mathematik. 1959, 1: 269–271, http://www-m3.ma.tum.de/foswiki/pub/MN0506/WebHome/dijkstra.pdf
    link
    (參考自維基中文https://zh.wikipedia.org/wiki/%E6%88%B4%E5%85%8B%E6%96%AF%E7%89%B9%E6%8B%89%E7%AE%97%E6%B3%95#cite_note-Dijkstra1959-9 )

  • Dijkstra 原始論文 期刊網頁, 要付費才看的到內容:
    Dijkstra, E.W. A note on two problems in connexion with graphs. Numer. Math. 1, 269–271 (1959). https://doi.org/10.1007/BF01386390
    (E. W. Dijkstra , Numerische Mathematik volume 1, pages269–271 (1959),
    https://link.springer.com/article/10.1007/BF01386390 link )
    (參考自維基 https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm#CITEREFDijkstra1959)

  • Dijkstra, Edsger W., 對 最短路徑原始論文 的回響: Reflections on "A note on two problems in connexion with graphs, https://www.cs.utexas.edu/users/EWD/ewd08xx/EWD841a.PDF link. (參考自維基 https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm#CITEREFDijkstra1959)

  • R. Neapolitan and K. Naimipour, Foundations of ALGORITHMS Using C++ Pseudocode, Jones and Bartlett Publishers. 蔡宗翰譯,碁峰出版.

  • Hiller and Lieberman, Operations Research, McGraw-Hill, 2005.

  • Michael Sambol, Dijkstra’s algorithm in 3 minutes, https://youtu.be/_lHSawdgXpI link

  • Michael Sambol, Bellman-Ford in 4 minutes — Theory, https://youtu.be/9PHkk0UavIM link

  • Michael Sambol, Bellman-Ford in 5 minutes — Step by step example, https://youtu.be/obWXjtg0L64 link

  • Wiki: Floyd–Warshall algorithm, https://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm link

  • Michael Sambol 的 Python codes, https://github.com/msambol/youtube/tree/master/shortest_path link

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值