【数据结构·考研】Dijkstra算法

边上权值非负情形的单源最短路径问题

Dijkstra算法的应用场景:给定一个带权有向图D与源点v,求从v到D中其他顶点的最短路径。

限定:各边权值大于或等于0

Dijkstra算法使用了广度优先搜索解决赋权有向图或者无向图的单源最短路径问题,算法最终得到一个最短路径树。

算法逐步求解过程:

①引入dis数组,它的每一个分量dis[i]表示当前找到的从源点v1到终点vi的最短路径长度。

②集合T是已求得最短路径的终点的集合,则可证明:下一条最短路径必然是从v1出发,中间只经过T中的顶点便可到达的那些顶点vx(vx∈V-T)的路径中的一条。

③每次求得一条最短路径之后,其终点vk加入集合T,然后对所有的D=vi(vi∈V-T)修改其dis[i]值。

Dijkstra算法属于贪心算法。

本文图例及部分算法思想参考博主Ouyang_Lianjun 的文章:

最短路径问题---Dijkstra算法详解

图例如下:

 

①首先第一步,我们先声明一个dis数组,该数组初始化的值为:

https://blog.csdn.net/cjw838982809

我们的顶点集T的初始化为:T={v1}。

②先找一个离v1顶点最近的顶点。通过数组dis[]可知当前离v1顶点最近是v3顶点。即 v1顶点到 v3顶点的最短路程就是当前 dis[2]值。将V3加入到T中。 

"为什么呢?因为目前离 v1顶点最近的是 v3顶点,并且这个图所有的边都是正数,那么肯定不可能通过第三个顶点中转,使得 v1顶点到 v3顶点的路程进一步缩短了。因为 v1顶点到其它顶点的路程肯定没有 v1到 v3顶点短。"

这句话要细品!你细品。这也是Dijkstra手动操作时的核心思想。

我们观察这个新加入的顶点,以v3 为弧尾的有 < v3,v4 >,那么我们看看路径:v1-v3-v4的长度是否比v1-v4短呢,dis[3]代表的v1–v4的长度为无穷大,而v1–v3–v4的长度为:10+50=60,所以更新dis[3]的值,得到如下结果: 

https://blog.csdn.net/cjw838982809

紫色代表已经固定的点,红色代表修改的点。

我们的顶点集T变为:T={v1,v3}。

 dis[3]要更新为 60。这个过程有个专业术语叫做“松弛”。即 v1顶点到 v4顶点的路程即 dis[3],通过 < v3,v4> 这条边松弛成功。这便是 Dijkstra 算法的主要思想:通过“边”来松弛v1顶点到其余各个顶点的路程。

③然后,我们又从除dis[2]和dis[0]外的其他值中寻找最小值,发现dis[4]的值最小,通过之前是解释的原理,可以知道v1到v5的最短距离就是dis[4]的值,然后,我们把v5加入到集合T中,考虑v5的出度是否会影响我们的数组dis的值,v5有两条出度:< v5,v4>和 < v5,v6>,然后我们发现:v1–v5–v4的长度为:50,而dis[3]的值为60,所以我们要更新dis[3]的值.另外,v1-v5-v6的长度为:90,而dis[5]为100,所以我们需要更新dis[5]的值。更新后的dis数组如下图: 

https://blog.csdn.net/cjw838982809

我们的顶点集T变为:T={v1,v3,v5}。

④然后,继续从dis中选择未确定的顶点的值中选择一个最小的值,发现dis[3]的值是最小的,所以把v4加入到集合T中,此时集合T={v1,v3,v5,v4},然后,考虑v4的出度是否会影响我们的数组dis的值,v4有一条出度:< v4,v6>,然后我们发现:v1–v5–v4–v6的长度为:60,而dis[5]的值为90,所以我们要更新dis[5]的值,更新后的dis数组如下图: 

https://blog.csdn.net/cjw838982809

我们的顶点集T变为:T={v1,v3,v5,v4}。

⑤然后加入V6:

https://blog.csdn.net/cjw838982809

最后我们的顶点集T变为:T={v1,v3,v5,v4,v6}。

不难看出Dijkstra算法的每一趟都会确定一个顶点。

我这里给出一个简单易懂的阉割版实现,看起来要更通俗易懂一点:

#include<iostream>
#include<vector>
using namespace std;
#define N 6

//求解单源最短路径的Dijkstra算法 
void Dijkstra(vector<vector<int> >& Graph,vector<int>& dis,vector<int>& path,vector<int>& T,int v){
	/*Graph是一个带权有向图
	  dis[j]是当前求到的从顶点v到顶点j的最短路径长度
	  T是最短路径顶点集
	  path[j]最短路径时j顶点的前一个顶点
	  整个过程从顶点v出发*/ 
	vector<bool> visited(N,false); //访问标记数组 
        int i,j,k;
        //dis[]赋初值
	for(i = 0;i < N;i ++){
		dis[i] = Graph[v][i]; 
		if(i != v && dis[i] < INT_MAX) 
			path[i]=v;
	}
	T.push_back(v); //顶点v加入顶点集合 
	visited[v] = true; //加入顶点集后置1 
        
        //输出初始状态下dis[],便于我们理解
	for(int m=0;m<N;m++){
		if(dis[m] == INT_MAX) cout<<"∞ "; 
		else cout<<dis[m]<<"  ";
	}
	cout<<endl;
	
        //每趟定一个点	
	for(i = 0;i < N;i ++){
		int min = INT_MAX;
		int u = v; //选不在T中的具有最短路径的顶点u
		for(j = 0;j < N;j ++){
			//找当前最小dis[]值 
			if(!visited[j] && dis[j] < min){
				u = j;
				min = dis[j];
			}
		}
		//存在u则加入顶点集
		if(!visited[u]){  
			T.push_back(u);
			visited[u] = 1;
		}
		
                //找需要标定的下一个点
		for(k = 0;k < N;k ++){
			int w=Graph[u][k];
			if(!visited[k] && w < INT_MAX && dis[u] + w < dis[k]){
				dis[k] = dis[u] + w;
				path[k]=u;
			}
		}
		
		//更新dis[],观察逐步求解过程 
		for(int m=0;m<N;m++){
			if(dis[m] == INT_MAX) cout<<"∞ "; 
			else cout<<dis[m]<<"  ";
		}
		cout<<endl;
	}	
}

int main(){
	//邻接矩阵 
	vector<vector<int> > Graph(N,vector<int>(N,INT_MAX)); 
	Graph[0][2] = 10;
	Graph[0][4] = 30;
	Graph[0][5] = 100;
	Graph[1][2] = 5;
	Graph[2][3] = 50;
	Graph[3][5] = 10;
	Graph[4][3] = 20;
	Graph[4][5] = 60;
	
	vector<int> dis(N);
	vector<int> path(N,INT_MAX);
	vector<int> T; //最短路径顶点集
	int v = 0; //从顶点v1开始,下标为0 
	
	cout<<"逐步求解过程: "<<endl; 
	Dijkstra(Graph,dis,path,T,v);
	
	cout<<"单源最短路径:"<<endl; 
	for(int i=0;i<T.size();i++) cout<<"v"<<T[i]+1<<" ";
	cout<<endl;
	
	cout<<"当前顶点的前一个顶点: "<<endl; 
	for(int i=0;i<N;i++){
		if(path[i] == INT_MAX) cout<<"∞ "; 
		else cout<<"v"<<path[i]+1<<"  ";
	}
}

时间复杂度:O(n^2)。算法运行时间取决于函数中的两层循环。

空间复杂度:O(n)。这里对dis数组的处理类似滚动数组的优化。

程序运行结果:

https://blog.csdn.net/cjw838982809

由结果可以看出Dijkstra算法求单源最短路径的过程,以及表示每个终点顶点前一个顶点的path数组。

求path数组是有意义的,比如现在题目问你v1顶点到v4顶点的最短路径。

那么我们可以倒着往回推:

v4<-v5 v4顶点的前一个顶点是v5;

v4<-v5<-v1 v5顶点的前一个结点是v1。

这样我们就得到了从v1到v4的最短路径:v1->v5->v4。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jiawen9

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值