# [資料結構] CH18. Shortest Path Algorithms ## Topological Sorting * 中文叫**拓撲排序**,是用於有向無環圖(directed acyclic graph,DAG)的一種線性排序。 * 如果DAG的點A指向點B,拓撲排序中點A一定要在點B前面。 * 一張DAG至少有一種拓撲排序,但可能有更多。 * 下圖的拓撲排序共有: * ABCDE * ABCED * ACBED * ACBDE ```graphviz digraph topologicalSort { A->B->E A->C->D B->D C->E } ``` ## Spanning Tree * 一張graph的生成樹是該graph的subgraph,且該subgraph所有點都被連到且沒有circle的出現。 * 以下為範例,但該圖的Spanning Tree還有很多張。 ```graphviz graph span { subgraph cluster1 { label="原圖" A[label="A"] B[label="B"] C[label="C"] D[label="D"] A--B--C--A--D--C; B--D {rank="same";A;B} {rank="same";C;D} } subgraph cluster2 { label="Spanning Tree 1" A2[label="A"] B2[label="B"] C2[label="C"] D2[label="D"] A2--B2--C2--D2; {rank="same";A2;B2} {rank="same";C2;D2} } subgraph cluster3 { label="Spanning Tree 2" A3[label="A"] B3[label="B"] C3[label="C"] D3[label="D"] A3--B3--C3 B3--D3 {rank="same";A3;B3} {rank="same";C3;D3} } } ``` ## Minimum Spanning Tree * 在討論最小生成樹之前,我們要先把權重加上去,沒有權重就不知道怎麼比大小了。 ```graphviz graph span { subgraph cluster1 { label="原圖(加權重)" A[label="A"] B[label="B"] C[label="C"] D[label="D"] A--B[label="1"] B--C[label="2"] C--A[label="3"] A--D[label="4"] D--C[label="5"] B--D[label="6"] {rank="same";A;B} {rank="same";C;D} } } ``` * 最小生成樹就是所有權重相加之後,總和最小的圖。 * 以上圖為例子,該圖的最小生成樹就是: ```graphviz graph span { subgraph cluster1 { label="最小生成樹" A[label="A"] B[label="B"] C[label="C"] D[label="D"] A--B[label="1"] B--C[label="2"] A--D[label="4"] {rank="same";A;B} {rank="same";C;D} } } ``` ## Shortest Path Algorithms * 現在問題來了,我們要如何從一張圖找出它的最小生成樹? * 一共有三種演算法可以幫我們找最短路徑 * Prim’s algorithm * Kruskal’s algorithm * Dijkstra’s algorithm * 其中前面兩個可以找到最小生成樹,而Dijkstra’s algorithm則是可以找出任意節點到其他所有點的最短距離。 ### Prim’s algorithm * 條件:給予所有edge的兩點與權重,和一個起始點。 * 做法:從起始點開始,在與其相連且尚未被選取的節點裡,選擇權重最小的邊,將新的節點加入。重複至所有點都被加入。 * 範例:使用Prim’s algorithm,以A為起始點找出MST。 ```graphviz graph prim { A--B[label="7"] A--C[label="3"] B--E[label="11"] C--D[label="10"] C--B[label="4"] D--B[label="9"] {rank="same";A;B;E} {rank="same";C;D} } ``` * 將點A加入,目前探索到的邊為AB和AC。 ```graphviz graph prim { A[color="red"] A--B[label="7",color="red"] A--C[label="3",color="red"] B--E[label="11"] C--D[label="10"] C--B[label="4"] D--B[label="9"] {rank="same";A;B;E} {rank="same";C;D} } ``` * 因為AC權重最小,將點C加入。點C加入後又多了BC和CD兩條邊。 ```graphviz graph prim { A[color="red"] C[color="red"] A--B[label="7",color="red"] A--C[label="3",color="blue"] B--E[label="11"] C--D[label="10",color="red"] C--B[label="4",color="red"] D--B[label="9"] {rank="same";A;B;E} {rank="same";C;D} } ``` * BC權重最小,加入點B。多了BD和BE兩條邊。 ```graphviz graph prim { A[color="red"] B[color="red"] C[color="red"] A--B[label="7",color="red"] A--C[label="3",color="blue"] B--E[label="11",color="red"] C--D[label="10",color="red"] C--B[label="4",color="blue"] D--B[label="9",color="red"] {rank="same";A;B;E} {rank="same";C;D} } ``` * AB權重最小,但AB兩點都已經加入,若選擇AB會形成circle,所以將AB捨棄。 ```graphviz graph prim { A[color="red"] B[color="red"] C[color="red"] A--B[label="7",color="red",style="dashed"] A--C[label="3",color="blue"] B--E[label="11",color="red"] C--D[label="10",color="red"] C--B[label="4",color="blue"] D--B[label="9",color="red"] {rank="same";A;B;E} {rank="same";C;D} } ``` * 接下來是BD權重最小。 ```graphviz graph prim { A[color="red"] B[color="red"] C[color="red"] D[color="red"] A--B[label="7",color="red",style="dashed"] A--C[label="3",color="blue"] B--E[label="11",color="red"] C--D[label="10",color="red"] C--B[label="4",color="blue"] D--B[label="9",color="blue"] {rank="same";A;B;E} {rank="same";C;D} } ``` * CD也都加入過了,捨棄。 ```graphviz graph prim { A[color="red"] B[color="red"] C[color="red"] D[color="red"] A--B[label="7",color="red",style="dashed"] A--C[label="3",color="blue"] B--E[label="11",color="red"] C--D[label="10",color="red",style="dashed"] C--B[label="4",color="blue"] D--B[label="9",color="blue"] {rank="same";A;B;E} {rank="same";C;D} } ``` * 最後是BE。 ```graphviz graph prim { A[color="red"] B[color="red"] C[color="red"] D[color="red"] E[color="red"] A--B[label="7",color="red",style="dashed"] A--C[label="3",color="blue"] B--E[label="11",color="blue"] C--D[label="10",color="red",style="dashed"] C--B[label="4",color="blue"] D--B[label="9",color="blue"] {rank="same";A;B;E} {rank="same";C;D} } ``` * 因此這張圖的MST為AC、BC、BD和BE。 ### Kruskal’s algorithm * 條件:給予所有edge的兩點與權重,**不需要**起始點。 * 做法:一開始所有的點都是獨立的樹。不停地將最小的邊所相連的兩個點合併成同一個樹(如果本來就是同一個樹就不能選擇那條邊),直到所有邊都跑過一輪。 * 範例:使用Kruskal’s algorithm找出MST。 ```graphviz graph kruskal { A[color="red"] B[color="blue"] C[color="orange"] D[color="purple"] E[color="gray"] A--B[label="3"] B--C[label="5"] C--D[label="2"] D--E[label="7"] A--E[label="1"] E--B[label="4"] E--C[label="6"] {rank="same";A;E} {rank="same";B;C;D} } ``` * 最小的邊是AE,將AE合併至同一個樹。 ```graphviz graph kruskal { A[color="red"] B[color="blue"] C[color="orange"] D[color="purple"] E[color="red"] A--B[label="3"] B--C[label="5"] C--D[label="2"] D--E[label="7"] A--E[label="1",color="red"] E--B[label="4"] E--C[label="6"] {rank="same";A;E} {rank="same";B;C;D} } ``` * 接下來是CD。 ```graphviz graph kruskal { A[color="red"] B[color="blue"] C[color="purple"] D[color="purple"] E[color="red"] A--B[label="3"] B--C[label="5"] C--D[label="2",color="red"] D--E[label="7"] A--E[label="1",color="red"] E--B[label="4"] E--C[label="6"] {rank="same";A;E} {rank="same";B;C;D} } ``` * 然後是AB。 ```graphviz graph kruskal { A[color="red"] B[color="red"] C[color="purple"] D[color="purple"] E[color="red"] A--B[label="3",color="red"] B--C[label="5"] C--D[label="2",color="red"] D--E[label="7"] A--E[label="1",color="red"] E--B[label="4"] E--C[label="6"] {rank="same";A;E} {rank="same";B;C;D} } ``` * BE已經在同一個樹了,所以不能使用這條邊合併。 ```graphviz graph kruskal { A[color="red"] B[color="red"] C[color="purple"] D[color="purple"] E[color="red"] A--B[label="3",color="red"] B--C[label="5"] C--D[label="2",color="red"] D--E[label="7"] A--E[label="1",color="red"] E--B[label="4",style="dashed"] E--C[label="6"] {rank="same";A;E} {rank="same";B;C;D} } ``` * BC合併所有點了。 ```graphviz graph kruskal { A[color="red"] B[color="red"] C[color="red"] D[color="red"] E[color="red"] A--B[label="3",color="red"] B--C[label="5",color="red"] C--D[label="2",color="red"] D--E[label="7"] A--E[label="1",color="red"] E--B[label="4",style="dashed"] E--C[label="6"] {rank="same";A;E} {rank="same";B;C;D} } ``` * 所有點都已經相連,因此MST是AE、AB、BC、CD。 ### Dijkstra’s algorithm * 條件:給予所有edge的兩點與權重(有向),和一個起始點。 * 做法:使用BFS的概念,不停搜索和更新起始點到所有點的最短距離。 * 範例:Dijkstra’s algorithm,以D為起始點找出到所有點的最短距離。 ```graphviz digraph dijkstra { B->A[label="4"] A->C[label="2"] D->B[label="15"] D->F[label="5"] D->G[label="23"] F->C[label="9"] F->G[label="13"] E->G[label="11"] E->B[label="17"] {rank="same";A;B} {rank="same";C;D;E} {rank="sink";F;G} } ``` * 從點D開始,因此已知最近距離是DB、DG、DF,其中最小的是DF,因此F設為已知最小值。 ```graphviz digraph dijkstra { B->A[label="4"] A->C[label="2"] D->B[label="15",color="blue"] D->F[label="5",color="red"] D->G[label="23",color="blue"] F->C[label="9",color="blue"] F->G[label="13",color="blue"] E->G[label="11"] E->B[label="17"] D[label="D 0"color="red"] F[label="F 5"color="red"] {rank="same";A;B} {rank="same";C;D;E} {rank="sink";F;G} } ``` * 這時要注意,FC的權重不是`9`,還要考慮點F本身是`5`,因此FC的權重是`14`。 * 同理,FG是`18`。 * 一樣挑選最小的權重點,將點C設為已知最小值。 ```graphviz digraph dijkstra { B->A[label="4"] A->C[label="2"] D->B[label="15",color="blue"] D->F[label="5",color="red"] D->G[label="23",color="blue"] F->C[label="9",color="red"] F->G[label="13",color="blue"] E->G[label="11"] E->B[label="17"] C[label="C 14"color="red"] D[label="D 0"color="red"] F[label="F 5"color="red"] {rank="same";A;B} {rank="same";C;D;E} {rank="sink";F;G} } ``` * 接下來是點B。 ```graphviz digraph dijkstra { B->A[label="4",color="blue"] A->C[label="2"] D->B[label="15",color="red"] D->F[label="5",color="red"] D->G[label="23",color="blue"] F->C[label="9",color="red"] F->G[label="13",color="blue"] E->G[label="11"] E->B[label="17"] B[label="B 15"color="red"] C[label="C 14"color="red"] D[label="D 0"color="red"] F[label="F 5"color="red"] {rank="same";A;B} {rank="same";C;D;E} {rank="sink";F;G} } ``` * 接下來是點G。 ```graphviz digraph dijkstra { B->A[label="4",color="blue"] A->C[label="2"] D->B[label="15",color="red"] D->F[label="5",color="red"] D->G[label="23",color="blue"] F->C[label="9",color="red"] F->G[label="13",color="red"] E->G[label="11"] E->B[label="17"] B[label="B 15"color="red"] C[label="C 14"color="red"] D[label="D 0"color="red"] F[label="F 5"color="red"] G[label="G 18"color="red"] {rank="same";A;B} {rank="same";C;D;E} {rank="sink";F;G} } ``` * 然後是點A。 ```graphviz digraph dijkstra { B->A[label="4",color="red"] A->C[label="2",color="blue"] D->B[label="15",color="red"] D->F[label="5",color="red"] D->G[label="23",color="blue"] F->C[label="9",color="red"] F->G[label="13",color="red"] E->G[label="11"] E->B[label="17"] A[label="A 19"color="red"] B[label="B 15"color="red"] C[label="C 14"color="red"] D[label="D 0"color="red"] F[label="F 5"color="red"] G[label="G 18"color="red"] {rank="same";A;B} {rank="same";C;D;E} {rank="sink";F;G} } ``` * 剩下的邊AC和DG都用不到,而EB和EG是無法使用。 * 因此從點D到所有點的最小距離就出來了: * A: 19 * B: 15 * C: 14 * D: 0(自己本身) * E: 無法到達 * F: 5 * G: 18 ### Prim和Kruskal的差異 * Prim和Kruskal都是求出MST的演算法,其中最大的差別在於: ```對於不是Connected graph的情況,Prim有可能壞掉,而Kruskal會直接給你每個連接的subgraph各自的MST``` ###### tags: `DS` `note`