單源最短路徑: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中的頂點爲中間頂點的當前最短路徑長度。
算法過程
- 初始時,S只包含源點V0,即S={V0},V0距源點V0的距離爲0。U包含除V0外的其他頂點。若U中某個頂點u是V0的出邊鄰接點,則V0到u的距離爲V0到u的弧的弧長,若u不是V0的出邊鄰接點,則u到V0的距離爲∞。將V0作爲基點。
- 在U中選出u0,使基點到u0的距離最小,將其加入S中。此時V0到u0的距離就是V0到u0的最短路徑的長度。
- 以u0爲基點,用從V0到u0的距離,對U中是u0的出邊鄰接點的所有點ux進行鬆弛操作,即如果源點V0經過u0到達ux的距離比原來記錄的V0到ux的距離小,則用經過u0到達ux的距離更新V0到ux的距離。
- 重複2和3,直到所有頂點都在S中。
動畫演示
(動畫來源)
反證
是否存在另一條路徑使得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;
}
示例圖:
運行結果: