Dijkstra算法的標記和結構與prim算法的用法十分相似。它們兩者都會從余下頂點的優先隊列中選擇下一個頂點來構造一顆擴展樹。但千萬不要把它們混淆了。它們解決的是不同的問題,因此,所操作的優先級也是以不同的方式計算的:Dijkstra算法比較路徑的長度,因此必須把邊的權重相加,而prim算法則直接比較給定的權重。
源最短路徑問題
給定一個帶權有向圖 G=(V,E) ,其中每條邊的權是一個非負實數。另外,還給定 V 中的一個頂點,稱為源。現在我們要計算從源到所有其他各頂點的最短路徑長度。這里的長度是指路上各邊權之和。這個問題通常稱為單源最短路徑問題。
前面Bellman-Ford最短路徑算法講了單源最短路徑的Bellman-Ford算法(動態規划算法)。這里介紹另外一個更常見的算法Dijkstra算法。
Dijkstra算法和 最小生成樹Prim算法最小生成樹算法非常類似,大家可以先熟悉下個算法。兩個算法都是基於貪心算法。雖然Dijkstra算法相對來說比Bellman-Ford 算法更快,但是不適用於有負權值邊的圖,貪心算法決定了它的目光短淺。而Bellman-Ford 算法從全局考慮,可以檢測到有負權值的回路。
這里模仿MST(Minimum Spanning Tree)的Prim算法,我們創建一個SPT(最短路徑樹),最初只包含源點。我們維護兩個集合,一組已經包含在SPT(最短路徑樹)中的頂點S集合,另一組是未包含在SPT內的頂點T集合。每次從T集合中選擇到S集合路徑最短的那個點,並加入到集合S中,並把這個點從集合T刪除。直到T集合為空為止。
舉例說明,如下圖所示的圖:
S集合最初為空,然后選取源點0,S集合為 {0},源點到其它所有點的距離為 {0, 4, INF, INF, INF, INF, 8, INF} 。圖中藍色表示 SPT,迭代的過程如下
最終得到 SPT(最短路徑樹) 如下:
算法C++實現:
我們使用Boolean 數組sptSet[] (也有習慣用visit[]命名,表示是否訪問過),來表示頂點是否為有SPT中。sptSet[v]=true,說明頂點v在SPT中。 dist[] 用來存儲源點到其它所有點的最短路徑。
#include<iostream> #include<stdio.h> #include<limits.h> using namespace std; const int V=9; //從未包含在SPT的集合T中,選取一個到S集合的最短距離的頂點 int getMinIndex(int dist[V],bool sptSet[V]) { int min=INT_MAX,min_index; for(int v=0;v<V;v++) { if(!sptSet[v]&&dist[v]<min) min=dist[v],min_index=v; } return min_index; } //打印結果 void printSolution(int dist[],int n) { printf("Vertex Distance from Source\n"); for(int i=0;i<V;i++) printf("%d\t\t %d\n",i,dist[i]); } //source 代表頂點 void dijkstra(int graph[V][V],int source) { int dist[V];// 存儲結果,從源點到 i的距離 bool sptSet[V]; // sptSet[i]=true 如果頂點i包含在SPT中 // 初始化. 0代表不可達 for(int i=0;i<V;i++) { dist[i]=(graph[source][i]==0)?INT_MAX:graph[source][i]; sptSet[i]=false; } // 源點,距離總是為0. 並加入SPT dist[source]=0; sptSet[source]=true; // 迭代V-1次,因此不用計算源點了,還剩下V-1個需要計算的頂點。 for(int count=0;count<V-1;count++) { // u,是T集合中,到S集合距離最小的點,u開始在T集合中,然后加入到S集合 int u=getMinIndex(dist,sptSet); // 加入SPT中 sptSet[u]=true; for(int v=0;v<V;v++) { //滿足以下4個條件 //1. 點v還沒有加入到spt中 //2. 點u和v之間可達 //3. 點u是可達的,距離不是無窮 //4. 經過點u之后的距離小於直接到達點v的距離 if(!sptSet[v]&&graph[u][v]&&dist[u]!=INT_MAX&&dist[u]+graph[u][v]<dist[v]) dist[v]=dist[u]+graph[u][v]; } } printSolution(dist,V); } int main() { int graph[V][V]={ { 0, 4, 0, 0, 0, 0, 0, 8, 0 }, { 4, 0, 8, 0, 0, 0, 0, 11, 0 }, {0, 8, 0, 7, 0, 4, 0, 0, 2 }, { 0, 0, 7, 0, 9, 14, 0, 0, 0 }, { 0, 0, 0, 9, 0, 10, 0, 0, 0 }, { 0, 0, 4, 0, 10, 0, 2, 0, 0 }, { 0, 0, 0, 14, 0, 2, 0, 1, 6 }, { 8, 11, 0, 0, 0, 0, 1, 0, 7 }, { 0, 0, 2, 0, 0, 0, 6, 7, 0 } }; dijkstra(graph, 0); return 0; }
運行結果如下:輸出源點0到其它各個頂點的最短距離: