單源最短路:Dijkstra算法 及 關於負權的討論


描述:

對於圖(有向無向都適用),求某一點到其他任一點的最短路徑(不能有負權邊)。

操作:

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。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM