dijkstra演算法 基礎版+鄰接表、優先佇列優化 雙版本
0 例題
Problem
在每年的校賽裡,所有進入決賽的同學都會獲得一件很漂亮的t-shirt。但是每當我們的工作人員把上百件的衣服從商店運回到賽場的時候,
卻是非常累的!所以現在他們想要尋找最短的從商店到賽場的路線,你可以幫助他們嗎?
Input
輸入包括多組資料。每組資料第一行是兩個整數N、M(N<=100,M<=10000),N表示成都的大街上有幾個路口,標號為1的路口是商店所在地,標號為N的路口是賽場所在地,M則表示在成都有幾條路。N=M=0表示輸入結束。接下來M行,每行包括3個整數A,B,C(1<=A,B<=N,1<=C<=1000),表示在路口A與路口B之間有一條路,我們的工作人員需要C分鐘的時間走過這條路。輸入保證至少存在1條商店到賽場的路線。
Output
對於每組輸入,輸出兩行行,第一行表示工作人員從商店走到賽場的最短時間,第二行表示路徑各點。
Sample Input
2 1
1 2 3
3 3
1 2 5
2 3 5
3 1 2
0 0
Sample Output
3
1 2
2
1 3
1 例題分析
本題求得是源點1到點N的最短路徑最少花費以及具體路徑點。所以很容易地想到求單源最短路徑求解演算法——dijkstra演算法。
在每條邊權值為非負實數的有向帶權圖中,給定一個點稱為源點,求該源點到其他點的最短路徑長度就是單源最短路徑問題。在帶權圖中,兩個頂點的路徑長度指它們之間的路徑上各邊的權值之和。
dijkstra演算法是解決單源最短路徑問題的貪心演算法,它先求出長度最短的一條路徑,然後參照該路徑求出長度次短的一條路徑,直到求出源點到其它各個頂點的最短路徑。
dijkstra演算法的基本思想是假定源點為u,頂點集合V被劃分為兩部分,集合S和集合V-S。初始時集合S中只有源點u,S中的頂點到遠點的最短路徑已確定,V-S中的頂點到源點的最短路徑待定。從源點出發只經過S中的點到達V-S中的點的路徑為特殊路徑,用陣列dist[]記錄每個頂點所對應的最短特殊路徑長度。
dijkstra演算法採用的貪心策略是選擇特殊路徑長度最短的路徑,將其連線的V-S中的頂點加入集合S,同時更新陣列dist[]。一旦S包含了所有頂點,dist[]就是從源點到其他所有頂點的最短路徑長度。
2 程式碼
2.1 鄰接矩陣原始程式碼
#include <iostream> #include <queue> #define MAX 1<<30 #define MAXN 1105 using namespace std; int map[MAXN][MAXN], dist[MAXN], flag[MAXN], p[MAXN], n, m; /*map[][]為鄰接矩陣,dist[]記錄各點到源點的距離; * flag[]記錄頂點的最短路徑是否已經找出;所有的點在V集合中,找出就放在S集合裡, * 否則就在V-S集合中,flag=1代表在S集合,否則在V-S集合中; * p[]是記錄最短路徑上某一頂點的前驅頂點*/ void dij(int); void findpath(int); int main() { while (cin >> n >> m, n && m) { for (int i = 0; i < 1105; i++) { for (int j = 0; j < 1105; j++) { map[i][j] = MAX; } } for (int i = 1; i <= m; i++) { int a, b, c; cin >> a >> b >> c; map[a][b] = c; map[b][a] = c; }/*儲存圖*/ dij(1); cout << dist[n] << endl; findpath(1); cout << endl; } return 0; } void dij(int v) { for (int i = 1; i <= n; i++) { dist[i] = map[v][i]; flag[i] = 0; if (dist[i] == MAX) { p[i] = -1;/*源點到該點的距離為無限大,說明源點與該點不相鄰*/ } else { p[i] = v;/*說明與源點v相鄰*/ } } flag[v] = 1;/*初始化dist[]和flag[]*/ for (int i = 1; i <= n; i++) { /*找到每個點距離源點最短路徑*/ int temp = MAX, t = v; for (int j = 1; j <= n; j++) { if (!flag[j] && dist[j] < temp) {/*找到V-S集合(flag==0代表屬於該集合)中距離源點最小的點,即dist[]最小的點*/ t = j; temp = dist[j]; } } if (t == v) { return; } flag[t] = 1;/*將找到的最小dist[]的點t加入S集合*/ for (int j = 1; j <= n; j++) {/*將找到的點t加入S集合後,更新與t相鄰的點的dist[]值*/ if (!flag[j] && map[t][j] < MAX) { if (dist[j] > temp + map[t][j]) { dist[j] = temp + map[t][j]; p[j] = t; } } } } } void findpath(int v) { deque<int> path; path.clear(); /*for (int i = 1; i <= n; i++) { int x = n; if (x == -1 && v != i) { cout << "源點到第" << i << "點" << ",無路可達" << endl; continue; } while (x != -1) { path.push_front(x); x = p[x]; } cout << "源點到第" << i << "點的最短路徑為:"; while (!path.empty()) { cout << path.front(); if (path.size() != 1) { cout << "--"; } path.pop_front(); } cout << endl; }*/ /*由於此題只需要找出第N點的最短路徑,所以不需要寫出上面的程式碼,上面的程式碼是所有點的最短路徑 * 本題的程式碼如下*/ int x = n; if (x == -1 && v != n) { cout << "no path" << endl; } while (x != -1) { path.push_front(x); x = p[x]; } cout << "path:"; while (!path.empty()) { cout << path.front(); if (path.size() != 1) { cout << "--"; } path.pop_front(); } cout << endl; }
2.2 鄰接表+優先佇列優化後的程式碼
dijkstra演算法實際上就是BFS演算法的“升級版”。BFS演算法每次選擇佇列中的隊首元素進行擴充套件,擴展出隊首元素的相鄰元素(並且未曾訪問過),將這些元素加入佇列;dijkstra演算法是BFS的升級版。當一個圖中的每條邊都加上權值後,BFS就沒辦法求一個點到另一個點的最短路徑了。這時候,需要用到dijkstra演算法。從最基本原理上講,把BFS改成dijkstra演算法,只需要把“佇列”改成“優先佇列”就可以了。