Dijkstra演算法(有權圖單源最短路徑)
阿新 • • 發佈:2018-11-08
從一個源點到其他各頂點的最短路徑問題稱為“單源最短路徑問題”。
-
最短路徑的最優子結構性質
- 該性質描述為:如果P(i,j)={Vi…Vk…Vs…Vj}是從頂點i到j的最短路徑,k和s是這條路徑上的一箇中間頂點,那麼P(k,s)必定是從k到s的最短路徑。下面證明該性質的正確性。
- 假設P(i,j)={Vi…Vk…Vs…Vj}是從頂點i到j的最短路徑,則有P(i,j)=P(i,k)+P(k,s)+P(s,j)。而P(k,s)不是從k到s的最短距離,那麼必定存在另一條從k到s的最短路徑P’(k,s),那麼P’(i,j)=P(i,k)+P’(k,s)+P(s,j)<P(i,j)。則與P(i,j)是從i到j的最短路徑相矛盾。因此該性質得證。
-
Dijkstra演算法
- 定義dist[W] = S到W的最短距離;
dist[S] = 0;
path[W] = S到W路徑上經過的頂點(即W前一個父頂點)。 - 由上述性質可知,如果存在一條從i到j的最短路徑(Vi…Vk,Vj),Vk是Vj前面的一頂點。那麼(Vi…Vk)也必定是從i到k的最短路徑。為了求出最短路徑,Dijkstra就提出了以最短路徑長度遞增,逐次生成最短路徑的演算法。 譬如對於源頂點s,
- 選擇其直接相鄰的頂點中長度最短的頂點V,此時將V收錄到集合S ={源點s + 已經確定了最短路徑的頂點V}中;
- V進入集合S後,將有可能影響V一圈鄰接點W的dist值,dist[W] = min{dist[W], dist[V] + <V, W>的權重};
- 最短路徑找到,停止。
- 定義dist[W] = S到W的最短距離;
-
虛擬碼
void Dijkstra(Vertex V) { while (1) { V = 未收錄頂點中dist最小者; if ( 這樣的V不存在) break; collected[V] = true; for ( V的每個鄰接點W ) if (collected[W] == false && dist[V] + E<V, W> < dist[W] ){ dist[W] = dist[V] + E<V, W>; path[W] = V; } } }
/*
Name: Dijkstra演算法
Copyright:
Author: xuuyann
Date: 07/11/18 14:31
Description:
*/
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <stack>
using namespace std;
//圖的鄰接矩陣表示法
#define MaxVertexNum 100 //最大頂點數設為100
#define INFINITY 65535 //無窮設為雙位元組無符號整數的最大值65535
typedef int Vertex;//用頂點下標表示頂點,為整型
typedef int WeightType;//邊的權值設為整型
typedef char DataType;//頂點儲存的資料型別設為字元型
//圖結點的定義
typedef struct GNode *PtrToGNode;
struct GNode {
int Nv;//頂點數
int Ne;//邊數
WeightType G[MaxVertexNum][MaxVertexNum];//鄰接矩陣,表示頂點間的相鄰關係
DataType Data[MaxVertexNum];//存頂點的資料
//注意:如果頂點無資料,此時Data[]可以不用出現
};
typedef PtrToGNode MGraph;//用鄰接矩陣儲存圖的型別
//邊的定義
typedef struct ENode *PtrToENode;
struct ENode {
int V1, V2;//邊的兩個頂點
WeightType Weight;//邊的權重
};
typedef PtrToENode Edge;
MGraph CreateGraph(int VertexNum)
//圖的建立,並初始化一個具有VertexNum個頂點且Edge為0的空圖
{
MGraph Graph = (MGraph)malloc(sizeof(struct GNode ));
Graph->Nv = VertexNum;
Graph->Ne = 0;
for (int i = 0; i < Graph->Nv ; i++){
for (int j = 0; j < Graph->Nv ; j++ )
Graph->G[i][j] = INFINITY;
}
return Graph;
}
void InsertEdge(MGraph Graph, Edge E)
//插入邊
{
Graph->G[E->V1 ][E->V2 ] = E->Weight ;
//若是無向圖,則還要插入邊<V2, V1>
//Graph->G[E->V2 ][E->V1 ] = E->Weight ;
}
Vertex FindMinDist(MGraph Graph, int *dist, int *collected)
//返回未被收錄頂點中dist最小者
{
Vertex MinV, V;
int MinDist = INFINITY;
for (V = 0; V < Graph->Nv ; V++){
if (collected[V] == false && dist[V] < MinDist){//如果V未被收錄並且dist[V]更小
MinDist = dist[V];//更新最小距離
MinV = V;//更新對應頂點
}
}
if (MinDist < INFINITY)//若找到最小dist
return MinV;
else
return -1;
}
bool Dijkstra(MGraph Graph, int *dist, int *path, Vertex S)
//dist[V]=S到V的最短距離,dist[S]=0,path[V]=S到V路上經過的父頂點
{
int collected[MaxVertexNum];
Vertex V, W;
//初始化:此處預設鄰接矩陣中不存在的邊用INFINITY
for (V = 0; V < Graph->Nv ; V++){
dist[V] = Graph->G[S][V];
if (dist[V] < INFINITY)
path[V] = S;//將S周圍一圈的鄰接點全賦予父頂點S
else
path[V] = -1;
collected[V] = false;
}
//先將源點S收入集合中
dist[S] = 0;//path[S] = -1
collected[S] = true;
while (1){
//V=未被收錄頂點中dist最小者
V = FindMinDist(Graph, dist, collected);
if (V == -1)
break;//演算法結束
collected[V] = true;
for (W = 0; W < Graph->Nv ; W++){//對於圖中每一個頂點W
if (collected[W] == false && Graph->G[V][W] < INFINITY){
//若W是V的鄰接點並且W未被收錄
if (Graph->G[V][W] < 0)
return false;//若有負值圈,則不能正確解決,返回錯誤標記
if (dist[V] + Graph->G[V][W] < dist[W]){
//該情況為,V進入集合S後可能影響另外一個W的dist,且隻影響V周圍一圈鄰接點的dist值
dist[W] = dist[V] + Graph->G[V][W];//更新dist[W]
path[W] = V;//更新S到W的路徑,即增加W的父頂點V
}
}
}
}
return true;//演算法執行完畢,返回正確標記
}
void PrintPath(int *path, Vertex W, Vertex S)
//列印最短路徑經過的所有頂點,此處列印0(path[0]=-1)到每個頂點的最短路徑
//先逆序push,再順序pop
{
stack<int> s;
s.push(W);//path[W] = V,path[S] = -1
while (W != S){
W = path[W];
s.push(W) ;
}//W = S時跳出迴圈,即到達源點
while (!s.empty() ){
if (s.size() == 1 )
printf("%d", s.top() );
else
printf("%d ", s.top() );
s.pop() ;
}
}
int main()
{
MGraph Graph;
int VertexNum, EdgeNum;
scanf("%d %d", &VertexNum, &EdgeNum);
int *dist = (int *)malloc(sizeof (int) * VertexNum);//初始化dist
int *path = (int *)malloc(sizeof (int) * VertexNum);//初始化path
Graph = CreateGraph(VertexNum);
Graph->Ne = EdgeNum;
Edge E = (Edge)malloc(sizeof(struct ENode ));
for(int i = 0; i < Graph->Ne ; i++){
scanf("%d %d %d", &E->V1 , &E->V2 , &E->Weight );
InsertEdge(Graph, E);
}//至此,圖建立完成
//下面進入Dijkstra演算法部分
int S = 0;//定義源點S,Graph->G[0][0]=INFINITY
Dijkstra(Graph, dist, path, S);
//PrintPath(path);
for (int i = 0; i < VertexNum; i++){
if (i != S){
PrintPath(path, i, S);
printf(" %d\n", dist[i]);
}
}
return 0;
}
- 測試資料:
- 測試結果: