Dijkstra算法

本文参考博客:学图论,你真的了解最短路吗? - 绝顶我为峰 的妙妙屋 - 洛谷博客

Dijkstra算法是一种求非负权值的单源最短路径算法。问题的提法是:给定一个有向带权图D与源点v,各边上的权值均为非负数,要求找出从v到D中其他各个顶点的最短路径


首先我们知道,如果起点v到终点u存在一条最短路径,那么该条路径的某一部分也一定是起点v到某个中途节点的最短路径,如下图的例子

假如黄色路径是0到4的最短路径,我们就可以知道0到8的最短路径一定是0 -> 1 -> 7 -> 8。若非如此,假如0到8之间存在一条更短的路径,如下图红色路径,

那么0到4之间就一定存在一条更短的路径,即红色路径加上8到4的黄色路径,便与原来的前提“黄色路径是0到4的最短路径”相矛盾。

说回Dijkstra算法,对于求最短路径,Dijkstra的思想是:用一个集合S存放已经求出最短路径的终点,每次操作找出与集合相邻且距离起点最近(即边权值最小)的点加入集合中,直到所有点都被加进集合内,算法结束。(看不懂没关系,看完下面的例子就懂了)
同时,算法引入了一个数组dist[],其中每个分量dist[i]为起点到第i个点的最短路径长度(初始化为无穷大,实际操作时可自己定义一个MAX_VALUE);若有需要,可以多引入一个数组path[]来记录最短路径,该数组的用法下文会进行介绍。

我们用一个例子来直观的感受一下Dijkstra算法的过程。

第一步,我们先把 a (起点)加入集合,不加粗的点为集合中的点,下同(s={a} ;dis[]={0,∞,∞,∞,∞,∞,∞,∞}):

第二步,我们找出与集合相邻且距离起点最近的点 b,把它加入集合,并确定它的最短路 0+2=2,存入数组(s={a,b} ;dis[]={0,2,∞,∞,∞,∞,∞,∞}):

第三步,我们找出与集合相邻且距离起点最近的点 d,把它加入集合,并确定它的最短路 2+1=3,存入数组(s={a,b,d} ;dis[]={0,2,∞,3,∞,∞,∞,∞}):

第四步,我们找出与集合相邻且距离起点最近的点 e,把它加入集合,并确定它的最短路 3+2=5(s={a,b,d,e} ;dis[]={0,2,∞,3,5,∞,∞,∞}):

第五步,我们找出与集合相邻且距离起点最近的点 f,把它加入集合,并确定它的最短路 0+9=90+9=9(s=\{a,b,d,e,f\}s={a,b,d,e,f} ;dis[]=\{0,2,∞,3,5,9,∞,∞\}dis[]={0,2,∞,3,5,9,∞,∞}):

第六步,我们找出与集合相邻且距离起点最近的点 g,把它加入集合,并确定它的最短路 5+7=12(s={a,b,d,e,f,g} ;dis[]={0,2,∞,3,5,9,12,∞}):

第七步,我们找出与集合相邻且距离起点最近的点 c,把它加入集合,并确定它的最短路 5+8=13(s={a,b,c,d,e,f,g} ;dis[]={0,2,13,3,5,9,12,∞}):

第八步,也是最后一步,我们找出与集合相邻且距离起点最近的点 h,把它加入集合,并确定它的最短路 13+5=18(s={a,b,c,d,e,f,g,h} ;dis[]={0,2,13,3,5,9,12,18}):

至此,整个图的最短路被我们求了出来,DijkstraDijkstra 顺利完成!

求出了最短路径长度,如果还想知道起点到终点的最短路径经过了哪些点怎么办?这时候就要用到前面提过的path[]数组了。其中path[i]表示在起点到i的最短路径上i的前驱节点。还是拿开篇的图做例子:

在该例子中,path[4]=5 (即在最短路径中,4的前驱节点是5)path[5]=2,path[2]=8,path[8]=7,path[7]=1,path[1]=0。

由此,我们便可以知道0到4的最短路径为0 -> 1 -> 7 -> 8 -> 2 -> 5 -> 4

而path[i]只需要在i放入集合s时可以顺便更新,具体看下面实现。

 思路出来了,下面就是算法的实现(C++)

(代码仅供参考,具体代码还需要根据图的实现来完成)

int n;//图顶点的个数
bool* s=new bool[n];//顶点集合,true表示起点到该顶点已找到最短路径
int* dist =new int[n];//起点到该顶点的最短路径长度
int* path=new int[n];//最短路径


void Dijkstra(Graph G,int v)//图G,起点v
{
	//初始化
	for (int i = 0; i < n; ++i)  
	{
		s[i] = false;

		dist[i] = G.getWeight(v, i);
		//最短路径设置为起点v到顶点i的权重,若两点间没有边,则为MAX_VALUE

		if (i != v && dist[i] < MAX_VALUE) path[i] = v;
		//已存在最短路径则将i的前驱设为v
		else path[i] = -1;      
		//否则设为-1
	}

	s[v] = true; dist[v] = 0;  //将起点加入集合

	/*每次循环会将一个顶点加入集合,起点已经加入了集合,
	因此进行n - 1次循环就可以求出起点所有顶点的最短路径*/
	for (int i = 0; i < n - 1; ++i)  
	{
		int u = v;  
		//u在下面的循环找出与集合s相邻且距离起点最近(即边权值最小)的点,此时dist[u]=MAX_VALUE

		for (int j = 0; j < n; ++j)
		{
			if (s[j] = false && dist[j] < dist[u])  u = j;  
			//找出不在集合内(即还没找到最短路径)且距离起点最近的顶点则更新
		}
		s[u] = true; //将找到的点加入集合

		for (int k = 0; k < n; ++k) //对于每个顶点
		{
			int w = G.getWeight(u, k);  //得出每个顶点与u之间的权重
			if (s[k] == false && w < MAX_VALUE && dist[u] + w < dist[k]) 
			//若该顶点没访问过且存在u到k的边且起点到u的最短路长度加上权重小于起点到k的最短路
			{
				dist[k] = dist[u] + w; //更新k的最短路
				path[k] = u;           //设置k的前驱
			}
		}

	}
}

容易知道该算法复杂度为O(n^2)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值