dijkstra算法是經典的貪心算法。基本的想法是,有兩個集合S和E,開始S集合中只有一個起點,E集合中有剩下的其他點。遍歷E集合中的所有點,找出與起點距離最近的一個點,將其加入集合S,並用該點去更新起點到其他點的最短路徑。(注意該算法只能處理正邊權的圖)
由動圖結合上面的思路,我們可以看出,算法的基本框架是:
1 1.初始化 2 for i(0 -> n - 1) 3 { 4 2.找出距離起點最近的點 5 3.標記該點加入集合S 6 4.用新加入集合S的點去更新起點到其他點的最短距離 7 }
1.其中初始化包括了距離數組dis,將dis數組初始化為無窮大,將第一個點的距離置為0。
2.循環n - 1次是因為n個點只需添加n - 1個點到集合S,每做一次循環添加一個點,所以是循環n - 1次。
3.標記新點加入集合用一個st數組記錄即可。
4.dis[i]表示的是起點s到i的點距離,所以用新點a去更新起點s到其他點(例如b點)的最短路徑就是比較dis[a] + (a到b的距離)和 dis[b]的大小,將dis[b]置成兩者中的最小值。
5.可以注意的是在偽代碼中的2步:找出距離起點最近的點,要遍歷剩下的所有點,所以時間復雜度是O(n),但是我們聯想到,在一堆數字中找出一個最小值,可以用堆進行優化,時間復雜度是可以由O(n)降到O(logn)的。所以這就是dijkstra的堆優化,可以將時間復雜度為O(mn)降為O(mlogn)。但是在稀疏圖中使用較好。稠密圖依舊是朴素的dijkstra算法效果更好。
完整代碼:
朴素版本:
時間復雜度O(mn)
1 #include <cstring> 2 #include <iostream> 3 #include <algorithm> 4 5 using namespace std; 6 7 const int N = 510; 8 9 int n, m; 10 int g[N][N]; 11 int dis[N]; 12 bool st[N]; 13 14 int dijkstra() 15 { 16 memset(dis, 0x3f, sizeof dis);//初始化 17 dis[1] = 0; 18 19 for (int i = 0; i < n - 1; i ++ ) 20 { 21 int t = -1; 22 for (int j = 1; j <= n; j ++ )//找出距離起點最近的點 23 if (!st[j] && (t == -1 || dis[t] > dis[j])) 24 t = j; 25 st[t] = true;//標記加入集合S 26 27 for (int j = 1; j <= n; j ++ )//用該點去更新其他點 28 dis[j] = min(dis[j], dis[t] + g[t][j]); 29 30 } 31 32 if (dis[n] == 0x3f3f3f3f) return -1; 33 return dis[n]; 34 } 35 36 int main() 37 { 38 cin >> n >> m; 39 40 memset(g, 0x3f, sizeof g); 41 while (m -- ) 42 { 43 int a, b, c; 44 cin >> a >> b >> c; 45 46 g[a][b] = min(g[a][b], c); 47 } 48 49 cout << dijkstra() << endl; 50 51 return 0; 52 } 53 54
堆優化版本:
時間復雜度O(mlogn)
1 #include <cstring> 2 #include <iostream> 3 #include <algorithm> 4 #include <queue> 5 6 using namespace std; 7 8 typedef pair<int, int> PII; 9 10 const int N = 1e5 + 10; 11 12 int n, m; 13 int h[N], w[N], e[N], ne[N], idx; 14 int dist[N]; 15 bool st[N]; 16 17 void add(int a, int b, int c) 18 { 19 e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ; 20 } 21 22 int dijkstra() 23 { 24 memset(dist, 0x3f, sizeof dist);//初始化 25 dist[1] = 0; 26 priority_queue<PII, vector<PII>, greater<PII>> heap;//小根堆 27 heap.push({0, 1});//第一個參數是距離,第二個參數是編號 28 // 注意這里st[1]不能置成true,這樣在下面的循環內,1號點就不能拓展,所以我們總是在拿出的時候將st置成true 29 while (heap.size()) 30 { 31 PII t = heap.top(); 32 heap.pop(); 33 34 int ver = t.second, distance = t.first; 35 36 if (st[ver]) continue;//如果已經加入集合過就跳過 37 st[ver] = true; 38 39 for (int i = h[ver]; i != -1; i = ne[i]) 40 { 41 int j = e[i]; 42 if (dist[j] > distance + w[i]) 43 { 44 dist[j] = distance + w[i]; 45 heap.push({dist[j], j}); 46 } 47 } 48 } 49 50 if (dist[n] == 0x3f3f3f3f) return -1; 51 return dist[n]; 52 } 53 54 int main() 55 { 56 cin >> n >> m; 57 58 memset(h, -1, sizeof h); 59 while (m -- ) 60 { 61 int a, b, c; 62 cin >> a >> b >> c; 63 add(a, b, c); 64 } 65 66 cout << dijkstra() << endl; 67 68 return 0; 69 }