單源最短路徑:Dijkstra算法

單源最短路徑:Dijkstra算法

概覽

Dijkstra算法(迪傑斯特拉算法)是由荷蘭計算機科學家Edsger Wybe Dijkstra提出,是典型的單源最短路徑算法,用於計算一個節點到其他所有節點的最短路徑。主要特點是以起始點爲中心向外層層擴展,直到擴展到終點爲止。
注意該算法要求圖中不存在負權邊。

問題

在帶權有向圖G=(V,A)中,假設每條弧A[i]的長度爲w[i],找到由頂點V0到其餘各點的最短路徑。

算法描述

算法思想

設G=(V,A)是一個帶權有向圖,把圖中頂點集合V分成兩組,第一組爲已求出最短路徑的頂點集合(用S表示,初始時S中只有一個源點V0,以後每求得一條最短路徑 , 就將加入到集合S中,直到全部頂點都加入到S中,算法就結束了),第二組爲其餘未確定最短路徑的頂點集合(用U表示),按最短路徑長度的遞增次序依次把第二組的頂點加入S中。在加入的過程中,總保持從源點V0到S中各頂點的最短路徑長度不大於從源點V0到U中任何頂點的最短路徑長度。此外,每個頂點對應一個距離,源點V0到S中的點的距離dist[i]就是從源點V0到此頂點的最短路徑長度,源點V0到U中的點的距離dist[i],是從V0到此頂點的,只包括S中的頂點爲中間頂點的當前最短路徑長度。

算法過程

  1. 初始時,S只包含源點V0,即S={V0},V0距源點V0的距離爲0。U包含除V0外的其他頂點。若U中某個頂點u是V0的出邊鄰接點,則V0到u的距離爲V0到u的弧的弧長,若u不是V0的出邊鄰接點,則u到V0的距離爲∞。將V0作爲基點。
  2. 在U中選出u0,使基點到u0的距離最小,將其加入S中。此時V0到u0的距離就是V0到u0的最短路徑的長度。
  3. 以u0爲基點,用從V0到u0的距離,對U中是u0的出邊鄰接點的所有點ux進行鬆弛操作,即如果源點V0經過u0到達ux的距離比原來記錄的V0到ux的距離小,則用經過u0到達ux的距離更新V0到ux的距離。
  4. 重複2和3,直到所有頂點都在S中。

動畫演示

Dijkstra算法動畫演示
動畫來源

反證

Dijkstra算法反證
是否存在另一條路徑使得A到C的距離更小?用反證法證明:
假設存在如上圖的紅色虛線路徑,使得A->D->C的距離更小,那麼A->D作爲A->D->C的一部分,其距離也比A->C小,這與C是U中到V0距離最小的點相矛盾,故假設不成立。
根據上面的證明,我們可以推斷出,Dijkstra算法每次循環都可以確定一個頂點的最短路徑,故需要循環n-1次。

程序代碼

/*
FILE:dijkstra.cpp
LANG:C++
*/
#include <iostream>
#include <cstring>
using namespace std;

const int node_num = 100;       //定義最大頂點數
const int INF = 2147483647;     //定義無窮
int matrix[node_num][node_num]; //鄰接矩陣
int dist[node_num];             //記錄源點到各個頂點的距離
int path[node_num];             //記錄前驅頂點
bool vis[node_num];             //標記頂點是否在集合S中
int v_num, a_num;               //記錄頂點數和弧的數量

void dijkstra(const int);

int main()
{
    cout << "v_num:";
    cin >> v_num;
    cout << "a_num:";
    cin >> a_num;

    for (int i = 0; i < v_num; ++i)
    {
        for (int j = 0; j < v_num; ++j)
        {
            matrix[i][j] = ((i != j) ? INF : 0);    //初始化鄰接矩陣
        }
    }

    int u, v, w;
    for (int i = 0; i < a_num; ++i)
    {
        cin >> u >> v >> w;
        matrix[u][v] = matrix[v][u] = w;    //示例爲一個無向圖
    }

    int src;
    cout << "source:";
    cin >> src;    //輸入源點

    dijkstra(src);

    for (int i = 0; i < v_num; ++i)
    {
        if (i != src)
        {
            cout << src << "->" << i << ":" << dist[i] << ":" << i;
            int t = path[i];
            while (t != src)
            {
                cout << "-" << t;
                t = path[t];
            }
            cout << "-" << src << endl;    //路徑是按倒序輸出的
        }
    }
    return 0;
}

void dijkstra(const int src)
{
    memset(vis, false, sizeof(vis));    //初始化集合S的標記
    vis[src] = true;    //源點進入集合S
    for (int i = 0; i < v_num; ++i)
    {
        dist[i] = matrix[src][i];    //初始化從源點到各點的距離
        path[i] = src;    //初始化前驅頂點
    }
    int min_cost, min_cost_index;    //記錄源點到U中各個頂點距離的最小值和頂點編號
    for (int i = 1; i < v_num; ++i)
    {
        min_cost = INF;
        for (int j = 0; j < v_num; ++j)    //找距離的最小值和對應的點u0
        {
            if (!vis[j] && dist[j] < min_cost)
            {
                min_cost = dist[j];
                min_cost_index = j;
            }
        }
        vis[min_cost_index] = true;    //u0進入集合S
        for (int j = 0; j < v_num; ++j)
        {
            if (!vis[j] && matrix[min_cost_index][j] != INF && min_cost + matrix[min_cost_index][j] < dist[j])    //如果U中頂點ux是u0的出邊鄰接點且源點經過u0到達ux的距離比原來記錄的源點到ux的距離小
            {
                dist[j] = min_cost + matrix[min_cost_index][j];    //執行鬆弛操作
                path[j] = min_cost_index;    //更新前驅頂點
            }
        }
    }
    return;
}

示例圖:
Dijkstra算法示例圖
運行結果:
Dijkstra運行結果

參考

  1. 最短路徑—Dijkstra算法和Floyd算法
  2. 單源最短路徑(1):Dijkstra 算法
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章