最短路径算法(上)——迪杰斯特拉(Dijikstra)算法

前记

对于迪杰斯特拉算法的分支界限法解法请移步:利用分支界限法求解Dijikstra算法


概述

单源最短路径问题,即在图中求出给定顶点到其它任一顶点的最短路径。在弄清楚如何求算单源最短路径问题之前,必须弄清楚最短路径的最优子结构性质。

最短路径的最优子结构性质描述为:如果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)

无权图的最短路径算法

对于无权图来说,可以把它当作每条边都为1的有权图。故无权图的最短路径算法如下:
1)初始化距离数组dist和路径数组path全为-1,同时定义一个队列queue,初始化队列为空。
2)把源点vertex入队并更新dist[vertex]=0
3)当队列不空是一直循环,利用cur_vertex保存出队元素,遍历cur_vertex的每个邻接点i,若dist[i] =-1,那么将其入队,更新dist[i] = dist[cur_vertex]+1,path[i] = cur_vertex。
算法如下:

//无权图的Dijikstra
void Unweighted(int vertex){
	queue queue;		//初始化队列
	queue.push(vertex);		//初始结点入队
	int cur_vertex;			//当前结点 
	this->dist[vertex] = 0;	//初始结点的距离为0 
	while(!queue.empty()){
		cur_vertex = queue.front();	//队头结点出队 
		queue.pop();
		//遍历cur_vertex的每个邻接点 
		for(int i = 1  ; i < this->Nv+1 ; i++){
			if((this->G[cur_vertex][i] == 1)&& (this->dist[i] == -1)){
			//当前结点的距离是cur_vertex的距离加1 
				this->dist[i] = this->dist[cur_vertex]+1;
			//把当前结点的上一个结点设为cur_vertex;
				this->path[i] = cur_vertex;	
				queue.push(i); 
			}
		}
	}
}

Dijikstra算法

Dijikstra算法主要是针对有权图的最短路径问题提出的,且具体问题中不能出现权值为负的边,即负值圈问题,如下图所示:
最短路径算法(上)——迪杰斯特拉(Dijikstra)算法_第1张图片
对于Dijikstra算法的理解,首先得从最短路径的最优子结构说起。(这部分引用海子的博客园的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)

那么Dijikstra算法描述如下:
假设存在G=,源顶点为V0,U={V0},dist[i]记录V0到i的最短距离,path[i]记录从V0到i路径上的i前面的一个顶点。
1)从V-U中选择使dist[i]值最小的顶点i,将i加入到U中;
2)更新与i直接相邻顶点的dist值。dist[j]=min{dist[j],dist[i]+matrix[i][j]}
3)直到U=V,算法停止。
起始Dijikstra算法的本质就是贪心算法。

//有权图的Dijikstra(遍历整个数组寻找最小路径顶点)
bool Dijikstra(int vertex){
	//根据初始结点初始化距离数组与路径数组 
	for(int i = 0 ; i < this->Nv+1 ; i++){
		//在构造函数里dist已经全部初始化为MAX
		//G存在边时为权重,没有边时为MAX 
		this->dist[i] = this->G[vertex][i];
		if(this->dist[i] < MAX){
			this->path[i] = vertex;
		}
	}
	this->dist[vertex] = 0;		//初始结点的距离为0
	this->collected[vertex] = 1;	//初始结点标记为已收录 
	while(1){
		//V是未被收录定点中dist最小者 
		int V = this->FindMinVertex(); 
		if(V == -1){//未找到这样的V则跳出循环 
			break;
		} 
		this->collected[V] = 1;//标记为已经被收录 
		//遍历图中每个顶点 
		for(int w = 1 ; w < this->Nv+1 ; w++){
			//若w是V的邻接点且未被收录 
			if(this->collected[w] == 0 && this->G[V][w] < MAX){
				if(this->G[V][w] < 0){//存在负边时 
					return false;	//结束算法 
				}
				//若收录V使得dist[w]变小 
				if(this->dist[V] + this->G[V][w] < this->dist[w]){
					//更新dist[w] 
					this->dist[w] = this->dist[V] = this->G[V][w]; 
					this->path[w] = V;//更新路径 
				} 
			} 
		} 
	}
	return true;
}

例子

对于无权图的最小路径我们基于如下的图模型:
最短路径算法(上)——迪杰斯特拉(Dijikstra)算法_第2张图片
全部代码:

#include 
#include 
#include 
#include 
using namespace std;

class Graph{
	private:
		int** G;	 			//邻接矩阵 
		int* dist;				//距离数组
		int* path;				//路径数组 
		int Nv;					//顶点数
		int Ne;					//边数 
	public:
		//构造函数 
		Graph(int nv , int ne){
			this->Nv = nv;
			this->Ne = ne;
			this->G = new int*[nv+1];
			this->dist = new int[nv+1];
			this->path = new int[nv+1];
			memset(this->dist,-1,sizeof(this->dist[0])*(nv+1));
			memset(this->path,-1,sizeof(this->path[0])*(nv+1));
			for(int i = 0 ; i < nv+1 ; i++){
				G[i] = new int[nv+1];
				memset(G[i],0,sizeof(G[i][0])*(nv+1));
			}
			cout<<"请输入边:"<>a>>b;
				this->G[a][b] = 1;
				this->G[b][a] = 1;
			}
		}
		
		//无权图的Dijikstra
		void Unweighted(int vertex){
			queue queue;		//初始化队列
			queue.push(vertex);		//初始结点入队
			int cur_vertex;			//当前结点 
			this->dist[vertex] = 0;		//初始结点的距离为0 
			while(!queue.empty()){
				cur_vertex = queue.front();	//队头结点出队 
				queue.pop();
				//遍历cur_vertex的每个邻接点 
				for(int i = 1  ; i < this->Nv+1 ; i++){
					if((this->G[cur_vertex][i] == 1)&& (this->dist[i] == -1)){
						this->dist[i] = this->dist[cur_vertex]+1;//当前结点的距离是cur_vertex的距离加1 
						this->path[i] = cur_vertex;	//把当前结点的上一个结点设为cur_vertex;
						queue.push(i); 
					}
				}
			}
		}
		
		//打印无权图迪杰斯特拉路径
		void Print_Unweighted(int vertex){
			for(int i = 1 ; i < this->Nv+1 ; i++){
				stack stack;
				stack.push(i);
				cout<path[j] != -1){//路径上的元素一次入栈 
					j = this->path[j];
					stack.push(j);	
				}
				//打印路径 
				cout< "<>nv>>ne;
	Graph graph(nv,ne);
	cout<<"请输入一个起始点:"<>vertex;
	graph.Unweighted(vertex);
	graph.Print_Unweighted(vertex); 
	
	return 0;
}

截图:
最短路径算法(上)——迪杰斯特拉(Dijikstra)算法_第3张图片

对于有权图的最短路径算法(Dijikstra)我们基于如下图模型:
最短路径算法(上)——迪杰斯特拉(Dijikstra)算法_第4张图片
全部代码:

#include 
#include 
#include 
#include 
using namespace std;

const int MAX = 65535;

class Graph{
	private:
		int** G;	 			//邻接矩阵 
		int* dist;				//距离数组
		int* path;				//路径数组
		int* collected;			//收录数组 
		int Nv;					//顶点数
		int Ne;					//边数 
	public:
		//构造函数 
		Graph(int nv , int ne){
			this->Nv = nv;
			this->Ne = ne;
			this->G = new int*[nv+1];
			this->dist = new int[nv+1];
			this->path = new int[nv+1];
			this->collected = new int[nv+1]; 
			for(int i = 0 ; i < this->Nv+1 ; i++){
				this->dist[i] = MAX;
			}
			memset(this->path,-1,sizeof(this->path[0])*(nv+1));
			memset(this->collected,0,sizeof(this->collected[0])*(nv+1));
			for(int i = 0 ; i < nv+1 ; i++){
				this->G[i] = new int[nv+1];
				for(int j = 0 ; j < nv+1 ; j++){
					this->G[i][j] = MAX; 
				}
			}
			cout<<"请输入边与权重:"<>v1>>v2>>weight;
				this->G[v1][v2] = weight;
				this->G[v2][v1] = weight;
			}
		}
		
		//遍历邻接点寻找最小距离顶点
		int FindMinVertex(){
			int MinDist = MAX;	//初始化最小距离
			int v,MinV = 0;
			for(v = 1 ; v < this->Nv+1 ; v++){
				if(this->collected[v] == 0 && this->dist[v] < MinDist){
					//v没有被收录且dist[v]更小
					MinDist = dist[v];
					MinV = v; 
				}
			}
			if(MinDist < MAX){//找到最小的dist 
				return MinV;	//返回对应顶点的下标 
			}else{
				return -1;	//若这样的顶点不存在则返回-1 
			} 
		} 
		
		//有权图的Dijikstra(遍历整个数组寻找最小路径顶点)
		bool Dijikstra(int vertex){
			//根据初始结点初始化距离数组与路径数组 
			for(int i = 0 ; i < this->Nv+1 ; i++){
				//在构造函数里dist已经全部初始化为MAX
				//G存在边时为权重,没有边时为MAX 
				this->dist[i] = this->G[vertex][i];
				if(this->dist[i] < MAX){
					this->path[i] = vertex;
				}
			}
			this->dist[vertex] = 0;		//初始结点的距离为0
			this->collected[vertex] = 1;	//初始结点标记为已收录 
			while(1){
				//V是未被收录定点中dist最小者 
				int V = this->FindMinVertex(); 
				if(V == -1){//未找到这样的V则跳出循环 
					break;
				} 
				this->collected[V] = 1;//标记为已经被收录 
				//遍历图中每个顶点 
				for(int w = 1 ; w < this->Nv+1 ; w++){
					//若w是V的邻接点且未被收录 
					if(this->collected[w] == 0 && this->G[V][w] < MAX){
						if(this->G[V][w] < 0){//存在负边时 
							return false;	//结束算法 
						}
						//若收录V使得dist[w]变小 
						if(this->dist[V] + this->G[V][w] < this->dist[w]){
							//更新dist[w] 
							this->dist[w] = this->dist[V] + this->G[V][w]; 
							this->path[w] = V;//更新路径 
						} 
					} 
				} 
			}
			return true;
		}
		
		//打印迪杰斯特拉路径
		void Print_Dijikstra(int vertex){
			for(int i = 1 ; i < this->Nv+1 ; i++){
				if(i == vertex){
					continue;
				} 
				stack stack;
				stack.push(i);
				cout<path[j] != -1){//路径上的元素一次入栈 
					j = this->path[j];
					stack.push(j);	
				}
				//打印路径 
				cout< "<>nv>>ne;
	Graph graph(nv,ne);
	cout<<"请输入一个起始点:"<>vertex;
	if(graph.Dijikstra(vertex)){
		graph.Print_Dijikstra(vertex); 	
	}
	
	return 0;
}

截图:
最短路径算法(上)——迪杰斯特拉(Dijikstra)算法_第5张图片

你可能感兴趣的:(#,数据结构,数据结构与算法专栏)