算法对比:
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];
}
}
}
- 通常将正无穷定义为99999999,因为这样即使两个正无穷相加,其和仍然不会超过int类型范围。
- 可以处理带有负权边,但是不能处理带有负权回路(负权环),因为带有负权环的图两点之间可能没有最短路径,每绕一次负权环,路径就会缩短一次,永远找不到最短路径是多少。
负权环: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数组