描述:
對於圖(有向無向都適用),求某一點到其他任一點的最短路徑(不能有負權邊)。
操作:
1. 初始化:
一個節點大小的數組dist[n]
源點的距離初始化為0,與源點直接相連的初始化為其權重,其他為無窮大(INT32_MAX等)。
標記源點,其到自身距離是0,已經是最小了。
2. 計算
對於dist,每次選取未標記的最小值(將其標記,表示已經得到最小值),更新與其相連的未標記的點:
如果此點加上權值,小於與其相連的點,則更新之。
代碼:
代碼並未優化,理解思路即可。
#include <string> #include <iostream> #include <vector> using namespace std; void Dijkstra() { int source = 0; // starting point int vex, edge; cout << "Input the number of vertexs and edges:" << endl; cin >> vex >> edge; vector<vector<int> > g = vector<vector<int> >(vex, vector<int>(vex)); // 用二維矩陣儲存 int start, end, weight; while(cin >> start >> end >> weight) g[start][end] = weight; vector<int> res(vex, INT32_MAX); vector<bool> visit(vex, false); res[source] = 0; visit[source] = true;
// 初始化 for (int i = 0; i < vex; ++i) if (i != source && g[source][i] != 0) res[i] = g[source][i];
// 兩層for循環 for (int i = 0; i < vex; ++i) { int min_element = INT32_MAX; int min_index = -1;
// 找出最小點 for (int j = 0; j < vex; ++j) { if (!visit[j] && res[j] < min_element) { min_index = j; min_element = res[j]; } } if (min_element == INT32_MAX || min_index == -1) break;
// 更新與其直接相連的所有點 for (int j = 0; j < vex; ++j) if (g[min_index][j] != 0 && g[min_index][j] + res[min_index] < res[j] &&
!visit[j]) { res[j] = g[min_index][j] + res[min_index]; visit[min_index] = true; } } for (int i = 0; i < vex; ++i) cout << res[i] << endl; }
證明:
一。第一次選擇:
初始數組是0, 30, 50, 20, 其他是無窮大。
此時選擇點3,標記,由於其他出路都大於20,不存在任一條路能更快到達3。
二。任一次選擇:
設我們選擇了與k相連的i點,將其標記。
需要證明不存在未標記的點j,使得存在一條路徑更快到達i點。
不可能的存在,因為如果存在則不可能標記 i,源點到達 j 的距離比到 i 距離近,而 j 前面必定存在被標記的一點s(至少是源點),至少從s開始標記。
證畢。(嚴格的推導可以設具體變量證明)
關於負權的討論:
要區分負權邊和負權環的概念。
負權邊:權重為負數的邊。
負權環:源點能到達的一個環,環上權重和為負數。
Dijkstra算法不能包含負權邊,含有負權環可以用bellman-ford算法計算。
即使不是負權環,Dijkstra也不能算,如果加入負邊,上面證明就不成立。
負權環不存在最短路!
關於Dijkstra算法不能計算負權邊:
不是說對於所有含有負權的圖都不能計算,如下可以計算:
當負權邊指向一個有唯一入邊的情況:
碰巧:
還有其他情況。所以,如果含有負權邊使用Dijkstra算法,可能對可能錯。
有些不能計算:
這種情況,第一次會選擇 點2,標記最短路徑為3,實際最短是0->1->2,距離為2。
復雜度:
用鄰接矩陣儲存的圖:
復雜度為O(V^2 + E),但E最大也不過V^2,也可以表示為O(V^2)。
原因:外層for循環,O(V)。內層,查找最小點O(V),更新這點的每條邊e,
則:V(V + e) => V^2 + Ve,總共E條邊,即V^2 + E。鄰接表也一樣。
用二叉堆維護最短距離數組:
復雜度為O((V + E)logV)。
原因:外層仍然是V。內層,取出最小值logV,更新點的e條邊,且需要入堆,則為elogV,
則:V(logV + elogV) => VlogV + VelogV => VlogV + ElogV => (V + E)logV。
關鍵在於堆本來是空的,每次更新,都將其入堆,然后從堆中選出最小的,將其標記,可以參考其實現代碼理解。
用斐波那契堆維護的最短距離數組:
復雜度:O(VlogV + E)
查下這種堆各操作復雜度即可知道,入堆為O(1),彈出為O(logN),則同上:
V(logV + e),即VlogV + E。