一、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;
}