圖」由節點和邊組成。上面的地鐵線路圖中從「芍藥居」出發到「太陽宮」須要3分種能夠用下「圖」表示。「圖」中描述了「A」和「B」互爲鄰節點,其中3表明從節點「A」到「B」那條邊的權重,邊有權重的圖稱爲「加權圖」,不帶權重的圖稱爲「非加權圖」。邊上的剪頭表明只能從A到B且須要的成本爲3,這種邊代有方向的圖稱爲「有向圖」。node
若是「A」能到「B」同時「B」也能夠到」A」且成本一樣爲3則稱爲「無向圖」算法
若是存在節「C」使得「A」到 「B」,「B」能夠到「C」,「C」又能夠到「A」則稱「A」、「B」、「C」爲一個「環」。 無向圖中每一條邊最可看爲一個環。測試
2.算法流程
spa
有以下「有向加權圖」, 咱們要從「起點」出發到「終點」。3d
首先須要四個表,用於存儲相關信息。code
表一: 用於存儲「圖」信息 「圖」信息blog
表二: 用於存儲每結點從起點出發的最小成本, 開始時只有「起點」成本爲0ip
表三:最小開銷路徑上每結點的父結點 rem
表四:記錄結點處理狀態get
算法流程以下:
1) 從表二及表四中找出最小開銷的未處理節點,開始時只有「起點」
2) 從表一中看到從起點出發能夠到達A和B開銷分別爲5和3,更新表二
3) 更新表三記錄當前到達A、B點的最小開銷父結點爲起點
4) 更新表四記錄已處理過起點,完成對一個節點的處理
5) (第二輪)從表二及表四中找出未處理過的最小開銷的節點「B」(到達成本3)
6) 從表一中看到從B出發能夠到達節點A和終點開銷分別爲1和4
7) 記錄B點已處理過
8) (第三輪)從表二及表四中找出未處理過的最小開銷的節點「A」
9) 從點A出表可到達終點,點A當前最小到達成本爲4 加上A到終點的開銷1小於表二中終點當前的最小開銷,因此更新表二中終點的開銷爲5 並更新表三中終點父節點爲A
10) 記錄A點已處理
11) (第四輪) 從表二及表四中找出未處理過的最小開銷的節點:「終點「
12) 因爲終點無指向結點無需再處理,支接標記已處理完成終點
13) (第五輪)已無未處理結點完成操做
14) 最終結果
從表二中咱們知道終點的最小到達開銷爲5
從表三中咱們能夠從終點的父結點一路推出最小開銷路徑爲: 終點 < A < B < 起點
4.代碼實現(TypeScript)
/** * 狄克斯特拉查找結果 */ export interface DijkstraFindResult<T_node> { /** 差找的圖 */ graph: Map<T_node, Map<T_node, number>>; /** 開始節點 */ startNode: T_node; /** 結束節點 */ endNode: T_node; /** 是否找到 */ isFind: boolean; /** 最小成本路徑節點鏈*/ parents: Map<T_node, T_node>; /** 結果路徑 */ path: T_node[]; /** 每節點最小到達成本 */ arriveCosts: Map<T_node, number>; } /** * 查找未處理過的最小成本節點 * @param costs key:節點信息, value:當前到達成本 * @param processed key:節點信息 value: 是否已處理過 */ function findMinCostNode<T_node>( costs: Map<T_node, number>, processed: Map<T_node, boolean>): T_node | null { var minCost: number = Number.MAX_VALUE; var minCostNode: T_node | null = null; for (const [node, cost] of costs) { if (cost < minCost && !processed.get(node)) { minCost = cost; minCostNode = node; } } return minCostNode; } /** * 返回從開始節點到結束節點路徑 * @param endNode 結束節點 * @param parents key:節點A value:節點A父節點 */ function getPath<T_node>( endNode: T_node, parents: Map<T_node, T_node>): T_node[] { let path = [endNode]; let nParent = parents.get(endNode); while (nParent) { path.push(nParent); nParent = parents.get(nParent); } path.reverse(); return path; } /** * 狄克斯特拉查找(找出成本最短路徑) * - 用於加權(無負權邊)有向圖無環圖 * @param graph 要查找的"圖", Map<節點 ,Map<相鄰節點,到達成本>> * @param startNode 開始節點 * @param endNode 結束節點 */ export function dijkstraFind<T_node>( graph: Map<T_node, Map<T_node, number>>, startNode: T_node, endNode: T_node): DijkstraFindResult<T_node> { /** 到節點最小成本 * k:節點 * v:從出發點到節點最小成本 */ let arriveCosts: Map<T_node, number> = new Map(); /** 最小成本路徑父節點 k:節點A v: 節點A在最小成本路徑上的父節點 */ let parents: Map<T_node, T_node> = new Map(); /** 已處理節點 k: 節點 v: 是否已處理過 */ let processedNode: Map<T_node, boolean> = new Map(); // 設置起點成本爲零 arriveCosts.set(startNode, 0); // 當前節點 let currentNode: T_node | null = startNode; // 當前節點到達成本 let currentNodeCost: number = 0; // 當前節點鄰節點 let neighbors: Map<T_node, number>; let isFind: boolean = false; while (currentNode) { // 標記是否找到目標結點 if (currentNode === endNode) isFind = true; // 這裏costs中必定會有node對映值因此強制轉型成number currentNodeCost = <number>arriveCosts.get(currentNode); neighbors = graph.get(currentNode) || new Map(); //遍歷鄰節點更新最小成本 for (const [neighborNode, neighborCost] of neighbors) { // 鄰節點以前算出的最小到達成本 let tmpPrevMinCost = arriveCosts.get(neighborNode); let prevCost: number = tmpPrevMinCost === undefined ? Number.MAX_VALUE : tmpPrevMinCost; // 鄰節點通過當前節點的成本 let newCost = currentNodeCost + neighborCost; // 若是經當前結點成本更小,更新成本記錄及鄰節點最小成本路徑父結點 if (newCost < prevCost) { arriveCosts.set(neighborNode, newCost); parents.set(neighborNode, <T_node>currentNode); } } // 記錄已處理結點 processedNode.set(<T_node>currentNode, true); // 找出下一個未處理的可到達最小成本結點 currentNode = findMinCostNode(arriveCosts, processedNode); } // 從起始點到終點路徑 let path: T_node[] = []; if (isFind) { path = getPath(endNode, parents); } return { isFind: isFind, path: path, graph: graph, arriveCosts: arriveCosts, parents: parents, startNode: startNode, endNode, }; } //eof dijkstraFind // 測試 function objToMap(obj: any): Map<string, number> { let map: Map<string, number> = new Map(); for (let k in obj) { map.set(k, obj[k]); } return map; } /** 圖 */ const graph: Map<string, Map<string, number>> = new Map(); graph.set("start", objToMap({ a: 5, b: 3 })); graph.set("a", objToMap({ end: 1 })); graph.set("b", objToMap({ a: 1, end: 4 })); graph.set("end", new Map()); let result = dijkstraFind(graph, "start", "end"); console.log(result); // 輸出 /* { isFind: true, path: [ 'start', 'b', 'a', 'end' ], graph: Map { 'start' => Map { 'a' => 5, 'b' => 3 }, 'a' => Map { 'start' => 5, 'end' => 1, 'b' => 1 }, 'b' => Map { 'start' => 3, 'end' => 4, 'a' => 1 }, 'end' => Map { 'a' => 1, 'b' => 4 } }, arriveCosts: Map { 'start' => 0, 'a' => 4, 'b' => 3, 'end' => 5 }, parents: Map { 'a' => 'b', 'b' => 'start', 'end' => 'a' }, startNode: 'start', endNode: 'end' } */
把上例中的「圖」當作一個地換線路圖:如今咱們要人A站到D站
將狄克斯特拉算法應用於地鐵圖對比上面的例子有幾個問題.
問題1: 地鐵爲一個無向圖,如A能夠到B,B也能夠到A ,因此描述圖信息時雙向的圖信息都 要錄入,如:
問題2:圖中第條邊都是一個環,且如A,B,C也可組成一個環是否會對結果產生影響?
不會,由於算法中每次選出的處理節點都是到達成本最小的節點,只有從這個節出發到下一個節點成本更底時纔會更新最小成本表和父節點表,且處理過的結點不會再次處理。
問題3: 如何處理換乘線路用時問題?
如:1號線換5號線須要2分種, 5號線換2號線要1分鐘。
上圖中咱們能夠看出不考慮換乘從A到D的最少用時路徑爲:
A > B > C > D
若是算上換乘線路時間最短用時路徑爲:
A > C > D
那麼如何處理呢?咱們能夠把換乘站內的換乘路徑當作一個局部的圖並將其嵌入地鐵圖中,如:
上圖中B結點由 B_A,B_D, B_C 三個結點代替。其中 B_A到B_C,B_D 到B_C 權重相同(也能夠不一樣)表明從1號線換5號線用時2分鐘,B_A到B_D權重爲0表明從A經由B到D不須要換乘。將上圖做爲新的算法輸入數據就可算出考慮換乘用時的最少用時路徑。
參考:
《算法圖解》【美】Aditya Dhargava
注:
狄克斯特拉算法部分主要參考算法圖解