图的最短路径(Floyd-Warshall算法、Dijkstra算法、Bellman-Ford算法)

一、Floyd-Warshall算法

求任意两点之间的最短距离
在这里插入图片描述

可以先写出它的邻接矩阵
在这里插入图片描述
假如现在只允许经过1号顶点,求任意两点之间的最短路程,应该如何求呢?只需判断e[i][1]+e[1][ j ]是否比e[i][j]要 小即可。e[i][j]表示的是从1号顶点到j号顶点之间的路程。e[i][1]+e[1][j]表示的是从i号顶点先到1号顶点,再从I号顶点到j号顶点的路程之和,i是1-n循环,j也是1~n循环,代码实现如下。

for(i=1;i<=n;i++)
{
	for(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号2号顶点,一直到允许经过1-n号顶点进行中转,求得两点间最短距离
最终代码

#include <iostream>
using namespace std;
#define inf 0x3f3f3f3f
int main()
{
	int e[10][10],n,m,t1,t2,t3;
	cin>>n>>m;//n表示顶点个数 m表示边的个数
	
	//邻接矩阵初始化 
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(i==j) e[i][j]=0;
			else e[i][j]=inf;
		}
	 } 
	 
	 for(int i=1;i<=m;i++)
	 {
	 	cin>>t1>>t2>>t3;
	 	e[t1][t2]=t3;
	 }
	 
	 //Floyd-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];
			 }
		 }
	  } 
	  
	  //输出结果
	  for(int i=1;i<=n;i++)
	  {
	  	for(int j=1;j<=n;j++)
	  	{
	  		cout<<e[i][j]<<" ";
		}
		cout<<endl;
	   } 
	return 0;
	
 } 

二、Dijkstra算法

求指定一个点(源点)到其余各个顶点的最短路径
例如:求下图的1号顶点到2、3、4、5、6号顶点的最短路径
在这里插入图片描述
这里仍然用邻接矩阵存储顶点与边的关系
在这里插入图片描述
还要用一个一维数组dis来存储1号顶点到其余各个顶点的初始距离
在这里插入图片描述
此时先找个离 1号顶点最近的项点即2号顶点。当选择了2号项点后,dis[2]的值就已经从“估计值”变为了“确定值”,即1号顶点到2号顶点的最短路程就是当前dis[2]值。

既然选了2号顶点,接下来再来看2号顶点有哪些出边。有2->3和2->4这两条边。先讨论通过2->3这条边能否让1号顶点到3号顶点的路程变短,也就是说现在来比较dis[3]和dis[2]+e[2][3]的大小。其中dis[3]表示1号顶点到3号顶点的路程: dis[2]+e[2][3]中 dis[2]表示1号顶点到2号顶点的路程,e[2][3]表示 2->3这条边。所以dis[2]+e[2][3]就表示从1号顶点先到2号顶点,再通过2->3这条边,到达3号顶点的路程。

我们发现dis[3]=12,dis[2]+e[2][3]=1+9=10, dis[3]>dis[2]+e[2][3], 因此dis[3]要更新为10。这个过程叫做“松弛”,1 号顶点到3号顶点的路程即dis[3],通过2->3这条边松弛成功。这便是Dijkstra算法的主要思想:通过“边”来松弛1号顶点到其余各个顶点的路程。

同理,通过2->4 (e[2][4]),可以将dis[4]的值从∞ 松弛为4 (dis([4]初始 为∞,dis[2]+e[2][4]=1+3=4 ,dis[4]>dis[2]+e[2][4], 因此dis[4]要更新为4)。

刚才我们对2号顶点所有的出边进行了松弛。松弛完毕之后dis数组为:
在这里插入图片描述
接下来,继续在剩下的3、4、5、6顶点中,选出离1号顶点最近的,通过上面的方法继续更新dis数组,最终结果为
在这里插入图片描述
代码:

#include <iostream>
using namespace std;
#define inf 0x3f3f3f3f
int main()
{
	int e[10][10],dis[10],book[10],n,m,t1,t2,t3,u,v,min;
	
	cin>>n>>m;//n表示顶点个数 m表示边的个数
	
	//邻接矩阵初始化 
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(i==j) e[i][j]=0;
			else e[i][j]=inf;
		}
	} 
	
	for(int i=1;i<=m;i++)
	{
	 	cin>>t1>>t2>>t3;
	 	e[t1][t2]=t3;
	}
	 
	//初始化dis数组,这里是1号顶点到其余各顶点的初始路程
	for(int i=1;i<=n;i++)
	{
		dis[i]=e[1][i];
	} 
	 
	for(int i=1;i<=n;i++)//book[i]=1代表当前点已确定距离 
	{
		book[i]=0;
	}
	book[1]=1;
	 
	//Dijkstra算法
	for(int i=1;i<=n-1;i++)
	{
		//找到离1号顶点最近的顶点
		min=inf;
		for(int j=1;j<=n;j++)
		{
			if(book[j]==0&&dis[j]<min)
			{
				min=dis[j];
				u=j;
			}
		} 
		book[u]=1;
		for(v=1;v<=n;v++)
		{
			if(e[u][v]<inf)//可以达到 
			{
				if(dis[v]>dis[u]+e[u][v])
				{
					dis[v]=dis[u]+e[u][v];
				}
			}
		} 		 
	} 
	
	for(int i=1;i<=n;i++)
	{
		cout<<dis[i]<<" ";
	}
	cout<<endl;
	 
	
	return 0;
}

三、Bellman-Ford------解决负权边

Dijkstra算法虽好,但他不能解决带有负权边(边的权值为负数)的图,因此给出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];	
	}	
} 
if(dis[v[i]]>dis[u[i]]+w[i])
			dis[v[i]]=dis[u[i]]+w[i];	

这两行代码意思是:看看能否通过u[i]->v[i](权值为w[i])这条边,使1号顶点到v[i]号顶点的距离变短。这一点与Dijkstra算法一样,然后把每一条边都松弛一遍

代码:

#include <iostream>
using namespace std;
#define inf 0x3f3f3f3f
int main()
{
	int dis[10],n,m,u[10],v[10],w[10];
	
	cin>>n>>m;//n表示顶点个数 m表示边的个数
	
	//读入边
	for(int i=1;i<=m;i++)
	{
		cin>>u[i]>>v[i]>>w[i];
	 } 
	 
	//初始化dis数组,这里是1号顶点到其余各顶点的初始路程
	for(int i=1;i<=n;i++)
	{
		dis[i]=inf;
	} 
	dis[1]=0;
	
	//Bellman-Ford算法核心语句 
	for(int k=1;k<=n-1;k++)//因为最短路径最多有n-1条边,所以最多循环n-1次 
	{
		for(int i=1;i<=m;i++)
		{	
			if(dis[v[i]]>dis[u[i]]+w[i])
				dis[v[i]]=dis[u[i]]+w[i];	
		}	
	} 
	
	//检测负权回路
	int flag=0;
	for(int i=1;i<=m;i++)
	{
		if(dis[v[i]]>dis[u[i]]+w[i])
			flag=1;
	} 
	if(flag==1) cout<<"此图含有负权回路"<<endl;
	
	for(int i=1;i<=n;i++)
	{
		cout<<dis[i]<<" ";
	}
	cout<<endl;
	 
	
	return 0;
}

下面这段代码是用来检测一个图是否含有负权回路

//检测负权回路
	int flag=0;
	for(int i=1;i<=m;i++)
	{
		if(dis[v[i]]>dis[u[i]]+w[i])
			flag=1;
	} 
	if(flag==1) cout<<"此图含有负权回路"<<endl;

但有时候Bellman-Ford算法在未达到n-1轮松弛就已经计算出结果,n-1是一个最大值,因此可以添加一个一维数组来备份数组dis,如果在新一轮松弛中数组dis未变化。则可以提前跳出循环。
代码如下:

#include <iostream>
using namespace std;
#define inf 0x3f3f3f3f
int main()
{
	int dis[10],bak[10],n,m,u[10],v[10],w[10],check;
	
	cin>>n>>m;//n表示顶点个数 m表示边的个数
	
	//读入边
	for(int i=1;i<=m;i++)
	{
		cin>>u[i]>>v[i]>>w[i];
	 } 
	 
	//初始化dis数组,这里是1号顶点到其余各顶点的初始路程
	for(int i=1;i<=n;i++)
	{
		dis[i]=inf;
	} 
	dis[1]=0;
	
	//Bellman-Ford算法核心语句 
	for(int k=1;k<=n-1;k++)//因为最短路径最多有n-1条边,所以最多循环n-1次 
	{
		//将dis数组备份至bak数组中
		for(int i=1;i<=n;i++) bak[i]=dis[i]; 
		//进行一轮松弛 
		for(int i=1;i<=m;i++)
		{	
			if(dis[v[i]]>dis[u[i]]+w[i])
				dis[v[i]]=dis[u[i]]+w[i];	
		}	
		
		//松弛后检测dis数组是否有更新
		check=0;
		for(int i=1;i<=n;i++)
		{
			if(bak[i]!=dis[i]) 
			{
				check=1;
				break;
			}
		} 
		if(check==0) break;//如果dis数组没有更新,提前跳出循环 
		
	} 
	
	//检测负权回路
	int flag=0;
	for(int i=1;i<=m;i++)
	{
		if(dis[v[i]]>dis[u[i]]+w[i])
			flag=1;
	} 
	if(flag==1) cout<<"此图含有负权回路"<<endl;
	
	for(int i=1;i<=n;i++)
	{
		cout<<dis[i]<<" ";
	}
	cout<<endl;
	 
	
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值