最短路径算法---总结 啊!哈算法 第六章

本文对Floyd - Warshall、Dijkstra、Bellman - Ford三种算法进行对比。Floyd - Warshall求任意两点最短路径,可处理负权边但不能处理负权回路;Dijkstra求指定点到其余各点最短路径,基于贪心思想,不能处理负权边;Bellman - Ford可解决负权边问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

 

算法对比:

 

Floyd——Warshall

求任意两个城市之间的最短路径

根据以往的经验,如果要让两点(a到b)之间的路程变短,只能引入第三个点,并通过这个顶点k中转,即a——>k——>b ,才能缩短原来从顶点a到顶点b的距离。甚至有时候我们会经过好多个点进行中转,用下图进行举例

如图:无穷代表两点之间没有路,把左图所代表的含义用右图矩阵的形式来表示,当任意两点之间不允许经过第三个点时,这些城市之间的最短路程如右图所示。如下。

假如经过1号点进行中转,求任意两点之间的最短路程,我们只需要判断

e[i][j]表示从i号顶点到j号顶点之间的路程;e[i][1]+e[1][j]表示从i号顶点到1号顶点,再从1号顶点到j号顶点的距离之和。

for(int i=1;i<=n;i++)
{
	for(int j=1;j<=n;j++)
	{
		if(e[i][j]>e[i]][1]+e[1][j])
		  e[i][j]=e[i][1]+e[1][j];
	}
}

这还只是经过1号顶点进行中转,中转之后如图:

有些路径经过中转,路径缩短了许多。

如果我们要是允许经过1~n号顶点进行中转,求任意两点之间的最短路径。

Floyed-Warshall的核心代码:

for(int k=1;k<=n;k++)
{
  for(int i=1;i<=n;i++)
  {
	for(int j=1;j<=n;j++)
	{
		if(e[i][j]>e[i]][k]+e[k][j])
		  e[i][j]=e[i][k]+e[k][j];
	}
  }	
}
  1. 通常将正无穷定义为99999999,因为这样即使两个正无穷相加,其和仍然不会超过int类型范围。
  2. 可以处理带有负权边,但是不能处理带有负权回路(负权环),因为带有负权环的图两点之间可能没有最短路径,每绕一次负权环,路径就会缩短一次,永远找不到最短路径是多少。

负权环:1--2--3 ,每转一次,路程就少1,。

Dijkstra

求指定一个点到其余各个顶点的最短路径

DijKstra算法也是用邻接矩阵来存储顶点与边之间的关系,初始状态如下:

      

还需要用一个以为数组dis来存储1号顶点到其余顶点的距离。顶点自己到自己的距离为0,两顶点之间没有边的距离设为无穷

既然是求1号顶点到各个顶点的距离,那就先找距离1号顶点距离最近的顶点。dis数组中值最小的顶点是2号顶点,所以选择2号顶点(1号顶点到其他顶点的路程肯定没有1号顶点到2号顶点的路程短),并且这个图所有的边都是正数,那么肯定不能通过第三个顶点中转,使得1号顶点到2号顶点之间的距离最短。

找到1——>2是最短距离之后,就在看2的出边有哪些,有2——>3和2——>4,首先先讨论,经过2——>3这个边,是否能让1号顶点到3号顶点之间的距离变短,dis[3]与dis[2]+e[2][3],意思就是1直接到3的距离 与 1直接到2,之后再有2到3距离之和相比较。

dis[3]=12 , dis[2]+e[2][3]=1+9=10,1号顶点经过2号顶点再到3号顶点,比1号顶点直接到3号顶点的距离要短,那么就更新dis[3]的值为10,这个过程叫“松弛”。

同理2——>4,dis[4]=无穷,dis[2]+e[2][4]=1+3=4,1 号顶点经过2号顶点到达4号顶点 比 1号顶点直接到达4号顶点的距离要少,所以就更新dis[4]=4.更新之后如下图

2号顶点找完了之后,就再找3号、4号、5号、6号各个顶点,从中选出距离1号顶点最近的顶点,很显然,通过上面的更新之后,是4号顶点距离1号顶点最近了,我们就再找1号顶点经过4号顶点又能松弛哪些点呢,4——>3,4——>5,4——>6,再通过上面的方法,松弛之后再更新dis数组,更新之后如图:

接着再找,这就是DijKstra算法找点的过程,更新完之后,dis数组中存放的就是1号顶点到各个顶点的最短距离:

DijKstra算法的核心代码:

const int inf=99999999;
int vis[maxn];//标记数组
int e[maxn][maxn];//来存放邻接矩阵 
for(int i=0;i<=n;i++)
  dis[i]=e[1][i];      //dis数组初始化 
for(int i=1;i<n-1;i++)  //在另外n-1个边中寻找 
{
	//找距离1号顶点最近的顶点 
    int min=inf;
    int temp;
	for(int j=1;j<=n;j++)
	{
	  if(vis[j]==0&&dis[j]<min)	
	  {
	  	  min==dis[j];
	  	  temp=j;
	  }
	}
	vis[temp]=1;
	for(int v=1;v<=n;v++)
	{
	   if(e[temp][v]<inf)
	   {
	   	 if(dis[v]>dis[temp]+e[temp][v])
	   	   dis[v]=dis[temp]+e[temp][v];
	   }	
	}	
}

DijKstra算法,属于贪心的思想,每次新扩展一个路程最短的点,更新与其相邻的点的路径。当所有边权都为正时,由于不会存在一个路程更短的没扩展过的点,所以这个点的路程永远不会再被改变,因而保证了算法的正确性。根据这个原理,是不能存在负权边的,如果存在负权边,在扩展到负权边的时候会产生更短的路程,就有可能破坏已经更新的点路程不会改变的性质。

举例如下图:: 破坏了已经更新过的点最短路程不会再改变这一性质。

 

Bellman——Ford

DijKstra算法虽然很好,但是不能解决带有负权边的图,而Bellman——Ford可以解决负权边问题,

Bellman——Ford算法的核心代码:

for(int k=1;k<=n-1;k++)
{
	for(int i=1;i<=m;i++)
	{
		if(dis[v[i]]>dis[u[i]]+w[i])
		  dis[v[i]]=dis[u[i]]+w[i];
	}
}

代码解释如下:

外层循环一共循环了n-1次(n为顶点的个数),内循环循环了m次(m为边的个数),即枚举每一条边。dis数组的作用与DijKstra算法一样,是用来记录源点到其余各个顶点的最短路径。u、v和w三个数组是用来记录边的信息,例如:第i条边存储在u[i]、v[i]、w[i]中,表示从顶点u[i]到顶点v[i]这条边(u[i]——>v[i])权值为w[i]。

if(dis[v[i]]>dis[u[i]]+w[i])
		  dis[v[i]]=dis[u[i]]+w[i];

上面这段代码的意思是:看是否能通过u[i]——>v[i]这条边,使得1号顶点到v[i]号顶点的距离边短。即1号顶点到u[i]号顶点的距离(dis[u[i]])加上u[i]——>v[i]这条边(权值为w[i])的值是否会比原来1号顶点到v[i]号顶点的距离(dis[v[i]])要小。

就像DijKstra算法的松弛操作一样。

示例如下图:

用dis数组来存放1号顶点到所有顶点的距离。不断地根据给定的边的顺序不断的更新dis数组

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值