最短路徑:dijkstra算法理解

設用帶權的鄰接矩陣arcs來表示帶權有向圖,arcs[i][j]表示弧<vi,vj>上的權值,若<vi,vj>不存在,則arcs[i][j]=無窮大。

dijklstra算法是一個單源(單個頂點出發)最短路,解決的是求一個頂點(這裏假設爲v0),到其他所有點的最短路徑的問題。

設集合S爲一個頂點集合,如果從源點v0到頂點vj的最短路徑已經確定,那麼點vj屬於集合S,它的初始狀態爲空集,設集合T=V-S爲圖中剩餘頂點集合,編程時可以使用長度爲n(n爲圖的頂點數目)的數組visited表示集合S,visited[i]=1表示第i個頂點屬於集合S,visited[i]=0表示第i個頂點屬於集合T。設置輔助數組D[n],D[i]表示從源點v0到頂點i的最短路徑長度

Dijkstra算法是最經典的最短路算法,用於計算正權圖的單源最短路(Single Source Shortest Path,源點給定,通過該算法可以求出起點到所有點的最短路),它是基於這樣一個事實:如果源點到k點的最短路已經求出,並且保存在D[k]上,那麼可以利用頂點k去更新點k能夠直接到達的所有點x的最短路(D[x] = min(D[x],D[k]+arcs[k][x]) )。
dijkstra算法:
(1)初始化:D[i] = arcs[v0][i], visited[i] = 0,visited[v0] = 1
(2)選擇vj,從集合T中選擇使得數組D中值最小的頂點vj,D[j] = Min{D[i]|vi屬於T},vj就是當前求得的下一條從v0出發的最短路徑的終點。將頂點j加入集合S中,令visited[j] = 1
(3)修改從v0出發到集合T上任意一頂點vk可達的最短路徑長度,如果D[j] + arcs[j][k] < D[k],則修改D[k] = D[j] + arcs[j][k]
(4)重複(2),(3)共n-1次,由此求得從v0到圖上其他頂點的最短路徑長度

如何理解

先了解一個非常重要的性質
性質:設S爲已求得最短路徑的終點的集合,可以證明:下一條最短路徑(設其終點爲x)或者是弧<v0, x>,或者是中間只經過S中的頂點而最後到達終點x的路徑。該性質的含義爲下一條最短路徑或者是從源點直接到達終點x,或者是只經過S中的頂點作爲中間頂點中轉到達終點x

反證法證明(嚴蔚敏老師的數據結構中的內容,加上自己的理解,如果不對請指出):S爲已求得最短路徑的終點的集合,假設下一條最短路徑爲a->b->c-x,其中a,b在集合S中,c,x在集合T中。這說明存在一條終點不在集合S中的路徑a->b->c,其長度比路徑a->b->c->x短。但是這是不可能的,因爲我們是按路徑長度遞增的次序來產生各最短路徑的,故長度比次路徑(a->b->c->x)短的所有路徑均已產生,他們的終點必定在S中,即假設不成立!

這裏需要解釋一下爲什麼集合T中使得數組D中值最小的頂點vj是下一條最短路徑的終點?這裏可以分兩種情況(下面所說的vj表示下一條最短路徑的終點)
①第一次執行步驟(2)時,這時S集合中只有源點v0,因爲D[vj]是最小的,即是從v0到vj是最短的路徑,因爲dijkstra算法只適用於正權圖,權值一定是正的,這時如果v0通過其他頂點作爲中間頂點再最後到達vj一定會增加權重,所以開始S中只有v0時,vj一定是下一條最短路徑的終點(間接說明了爲什麼dijkstra算法不適用與負權圖)。
②如果不是第一次執行步驟(2)時,集合S中有多個頂點。因爲該算法的核心爲步驟2,3的多次重複,所以當不是第一次執行步驟(2)時,這時必定是執行過步驟3再次回到步驟2,由上面的性質可知,vj一定是從v0直接到達vj或者是S中頂點作爲中間頂點再到達vj,而注意在步驟3中,更新數組D時,同時考慮了從v0直接到達各個頂點的長度和從v0經過S中頂點作爲中間頂點到達各個頂點的長度,並且取這兩種路徑較短的路徑賦值給D[k]( D[k]=min(D[j]+arcs[j][k], D[k]) )。因爲上面所述的性質限定了下一條最短路徑的終點只可能有這兩種情況產生,而在步驟(3)中已經取了這兩種情況的最短路徑,所以使得D最小的vj一定是下一條最短路徑的終點。

算法實現
圖用帶權鄰接矩陣存儲map[][]
數組distance[]存放當前找到的從源點V0到每個終點的最短路徑長度,其初態爲圖中直接路徑權值
數組parent[]表示從V0到各終點的最短路徑上,此頂點的前一頂點的序號;若從V0到某終點無路徑,則用-1作爲其前一頂點的序號

#include <climits>
#include <iostream>
#include <stdio.h>
#include <stack>
using namespace std;

const int INFI = 1000; //1000作爲最大值,沒有使用INT_MAX,因爲下面運算可能會造成溢出
int v, m;              //v爲圖的頂點數,m爲圖中邊的數目

void dijkstra(int v0, int map[][100], int parent[], int distance[], int visited[]) //源頂點從0開始
{
    for (int i = 1; i <= v; i++)
    {
        visited[i] = 0; //初始化所有點都在S集合中
        if (map[v0][i] < INFI)
            parent[i] = v0; //由源點直接到達
        else
            parent[i] = -1;
        distance[i] = map[v0][i];
    }
    visited[v0] = 1; //源點v0加入集合S中
    parent[v0] = -1; //起始點沒有父節點,設置爲-1
    //以上爲初始化工作

    //dijkstra算法核心
    for (int k = 1; k < v; k++) //循環v-1次,v0到其餘v-1個頂點的最短路徑
    {
        int minDis = INFI; //minDis爲以v0出發的最短的路徑長度
        int minIndex = -1;
        for (int i = 1; i <= v; i++) //找集合T到集合S最短距離的點
        {
            if (!visited[i] && distance[i] < minDis)
            {
                minIndex = i;
                minDis = distance[i];
            }
        }
        if (minIndex == -1)
            break;
        visited[minIndex] = 1; //將最短路徑的點加入集合S中

        for (int i = 1; i <= v; i++) //新的點加入S後,更新到達集合T中點的距離
        {
            if (!visited[i] && distance[i] > minDis + map[minIndex][i]) //更新集合S到集合T的距離
            {
                distance[i] = minDis + map[minIndex][i];
                parent[i] = minIndex; //通過minIndex到達終點i比之前的假設由v0直接到達終點i更短
            }
        }
    }
}

void print_shortestPath(int start, int end, int parent[])
{ //輸出從源點start到終點end的最短路徑
    stack<int> s;
    if(parent[end]==-1)
    {
        printf("不能從源點%d到底終點%d", start, end);
        return;
    }
        
    s.push(end);
    int k = parent[end];
    while(k!=-1){
        s.push(k);
        k = parent[k];
    }

    printf("從點 %d 到點 %d 的最短路徑爲:", start, end);
    while(!s.empty()){
        int top = s.top();
        cout << top << "->";
        s.pop();
    }
    cout << endl;
}

int main()
{
    cin >> v >> m;

    int map[100][100];
    int visited[v + 1] = {0}; //標記圖上的點是否已經加入最短路徑中
    int parent[v + 1];        //記錄每個頂點的前驅節點,也就是父節點
    int distance[v + 1];      //存儲最短路徑長度

    //初始化map
    for (int i = 1; i <= v; i++) //沒有使用第0行和第0列
        for (int j = 1; j <= v; j++)
        {
            map[i][j] = INFI;
            map[i][i] = 0;
        }
    for (int i = 0; i < m; i++) //輸入m條邊
    {
        int a, b, c;
        cin >> a >> b >> c;
        map[a][b] = c;
    }

    dijkstra(1, map, parent, distance, visited);

    cout << "distance數組(源點到各個點的最短路徑長度):";
    for (int i = 1; i <= v; i++)
    {
        cout << distance[i] << " ";
    }
    cout << endl;

    cout << "parent數組(最短路上終點的前一個節點):";
    for (int i = 1; i <= v; ++i)
    {
        cout << parent[i] << " ";
    }
    cout << endl;

    for (int i = 1; i <= v;i++){
        print_shortestPath(1, i, parent);
    }
        
    system("pause");
    return 0;
}
/*輸入
7 10
1 2 13
1 3 8
1 5 30
1 7 32
2 6 9
2 7 7
3 4 5
4 5 6
5 6 2
6 7 17

*/

輸入一下數據描述下圖中的圖
7 10
1 2 13
1 3 8
1 5 30
1 7 32
2 6 9
2 7 7
3 4 5
4 5 6
5 6 2
6 7 17
在這裏插入圖片描述

程序運行結果
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章