1. 程式人生 > >Dijkstra演算法(有權圖單源最短路徑)

Dijkstra演算法(有權圖單源最短路徑)

  從一個源點到其他各頂點的最短路徑問題稱為“單源最短路徑問題”。

  • 最短路徑的最優子結構性質

    • 該性質描述為:如果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>的權重};
      • 最短路徑找到,停止。

  • 虛擬碼

    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;
    			}
    	}
    }
    

  • 演算法複雜度

    • FinMinDist可以用小頂堆實現。
      在這裡插入圖片描述

  • 參考程式碼

/* 
	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;
	 
}

  • 測試資料:
    在這裡插入圖片描述

  • 測試結果:
    在這裡插入圖片描述

Reference