最短路径算法总结(Floyd,bellmen-ford,dijkstra,Spfa)

Dijkstra:适用于权值为非负的图的单源最短路径,用斐波那契堆的复杂度O(E+VlgV)
BellmanFord:适用于权值有负值的图的单源最短路径,并且能够检测负圈,复杂度O(VE) SPFA:适用于权值有负值,且没有负圈的图的单源最短路径,论文中的复杂度O(kE),k为每个节点进入Queue的次数,且k一般<=2,
但此处的复杂度证明是有问题的,其实SPFA的最坏情况应该是O(VE). Floyd:每对节点之间的最短路径。

Bellman-Ford算法

Bellman-Ford算法能在更普遍的情况下(存在负权边)解决单源点最短路径问题。对于给定的带权(有向或无向)图 G=(V,E),其源点为s,加权函数 w 是边集 E 的映射。对图G运行Bellman-Ford算法的结果是一个布尔值,表明图中是否存在着一个从源点s可达的负权回路。若不存在这样的回路,算法将给出从源点s到图G的任意顶点v的最短路径d[v]。

Bellman-Ford算法流程分为三个阶段:

(1)初始化:将除源点外的所有顶点的最短距离估计值 d[v] ←+∞, d[s] ←0;
(2)迭代求解:反复对边集E中的每条边进行松弛操作,使得顶点集V中的每个顶点v的最短距离估计值逐步逼近其最短距离;(运行|v|-1次)

(3)检验负权回路:判断边集E中的每一条边的两个端点是否收敛。如果存在未收敛的顶点,则算法返回false,表明问题无解;否则算法返回true,并且从源点可达的顶点v的最短距离保存在 d[v]中。

适用条件和范围:
  1.单源最短路径(从源点s到其它所有顶点v); 
  2.有向图&无向图(无向图可以看作(u,v),(v,u)同属于边集E的有向图); 
  3.边权可正可负(如有负权回路输出错误提示); 
  4.差分约束系统;

bool bellman()  
{  
    bool flag ;  
    for(int i=0;i<n-1;i++)  
    {  
        flag=false;  
        for(int j=0;j<all;j++) //穷举每条边 
            if(dis[t[j].to]>dis[t[j].from]+t[j].vis) //松弛判断 
            {  
                dis[t[j].to]=dis[t[j].from]+t[j].vis;  //松弛操作 
                flag=true;  
            }  
            if(!flag)  
            break;  
    }  
    for(int k=0;k<all;k++) //对所有边进行一次遍历,判断是否有负回路 
    if(dis[t[k].to]>dis[t[k].from]+t[k].vis)  
    return true;  
    return false;  
}  

Dijkstra算法

算法流程:
(a) 初始化:用起点v到该顶点w的直接边(弧)初始化最短路径,否则设为∞;
(b) 从未求得最短路径的终点中选择路径长度最小的终点u:即求得v到u的最短路径;
(c) 修改最短路径:计算u的邻接点的最短路径,若(v,…,u)+(u,w)<(v,…,w),则以(v,…,u,w)代替。
(d) 重复(b)-(c),直到求得v到其余所有顶点的最短路径。
特点:总是按照从小到大的顺序求得最短路径。

假设一共有N个节点,出发结点为s,需要一个一维数组vis[N]来记录前一个节点序号,一个一维数组dis[N]来记录从原点到当前节点最短路径(初始值为s到Vi的边的权值,没有则为+∞),一个二维数组map[N][N]来记录各点之间边的权重,按以上流程更新map[N]和dis[N]。

void dijs(int v)//v为原点   
{  
    int i,j,k;  
    for(i=1;i<=n;i++)  
    dis[i]=map[v][i];//初始化   
    memset(vis,0,sizeof(vis));  
    vis[v]=1;  
    for(i=2;i<=n;i++)  
    {  
        int min=INF;  
        k=v;  
        for(j=1;j<=n;j++)  
        {  
            if(!vis[j]&&min>dis[j])  
            {  
                k=j;  
                min=dis[j];//在dis中找出最小值   
             }   
        }  
        vis[k]=1;//使k为已生成终点   
        for(j=1;j<=n;j++)//修改dis   
        {  
            if(dis[j]>dis[k]+map[k][j])  
            dis[j]=dis[k]+map[k][j];  
        }  
    }  
}  

SPFA算法

求最短路径的算法有许多种,除了排序外,恐怕是OI界中解决同一类问题算法最多的了。最熟悉的无疑是Dijkstra,接着是Bellman-Ford,它们都可以求出由一个源点向其他各点的最短路径;如果我们想要求出每一对顶点之间的最短路径的话,还可以用Floyd-Warshall。

SPFA是这篇日志要写的一种算法,它的性能非常好,代码实现也并不复杂。特别是当图的规模大,用邻接矩阵存不下的时候,用SPFA则可以很方便地面对临接表。每个人都写过广搜,SPFA的实现和广搜非常相似。

如何求得最短路径的长度值?

首先说明,SPFA是一种单源最短路径算法,所以以下所说的“某点的最短路径长度”,指的是“某点到源点的最短路径长度”。

我们记源点为S,由源点到达点i的“当前最短路径”为D[i],开始时将所有D[i]初始化为无穷大,D[S]则初始化为0。算法所要做的,就是在运行过程中,不断尝试减小D[]数组的元素,最终将其中每一个元素减小到实际的最短路径。

过程中,我们要维护一个队列,开始时将源点置于队首,然后反复进行这样的操作,直到队列为空:

(1)从队首取出一个结点u,扫描所有由u结点可以一步到达的结点,具体的扫描过程,随存储方式的不同而不同;

(2)一旦发现有这样一个结点,记为v,满足D[v] > D[u] + w(u, v),则将D[v]的值减小,减小到和D[u] + w(u, v)相等。其中,w(u, v)为图中的边u-v的长度,由于u-v必相邻,所以这个长度一定已知(不然我们得到的也不叫一个完整的图);这种操作叫做松弛。

《最短路径算法总结(Floyd,bellmen-ford,dijkstra,Spfa)》之 SPFA算法” style=”border:0px;list-style:none;” class=”aligncenter myImgClass”> 引用内容
松弛操作的原理是著名的定理:“三角形两边之和大于第三边”,在信息学中我们叫它三角不等式。所谓对i,j进行松弛,就是判定是否d[j]>d[i]+w[i,j],如果该式成立则将d[j]减小到d[i]+w[i,j],否则不动。


(3)上一步中,我们认为我们“改进了”结点v的最短路径,结点v的当前路径长度D[v]相比于以前减小了一些,于是,与v相连的一些结点的路径长度可能会相应地减小。注意,是可能,而不是一定。但即使如此,我们仍然要将v加入到队列中等待处理,以保证这些结点的路径值在算法结束时被降至最优。当然,如果连接至v的边较多,算法运行中,结点v的路径长度可能会多次被改进,如果我们因此而将v加入队列多次,后续的工作无疑是冗余的。这样,就需要我们维护一个bool数组Inqueue[],来记录每一个结点是否已经在队列中。我们仅将尚未加入队列的点加入队列。

void spfa()
{
	int i,k;
	memset(vis,0,sizeof(vis));
	for(i=1;i<=n;i++)
		dis[i]=INF;//初始化 
	dis[1]=0;
	queue<int>q;//创建队列 
	vis[1]=1;
	q.push(1);//源点放入队尾 
	 
	while(!q.empty())
	{
		k=q.front();//从队首取出一个节点,扫描所有从该节点可以到达的终点 
		q.pop();
		vis[k]=0;
		for(i=1;i<=n;i++)
		{
			if(dis[i]>dis[k]+map[k][i])//松弛判断 
			{
				dis[i]=dis[k]+map[k][i];//松弛操作 
			    if(vis[i]==0)//判断这个点是否在队列里面,如果不在加入队列 
			    {
				    q.push(i);
				    vis[i]=1;
			    }
			}
			
		}
	}	
}


Floyd算法


这里需要用到动态规划的思想,对于任何一个城市而言,i 到 j 的最短距离不外乎存在经过 i 与 j 之间的k和不经过k两种可能,所以可以令k=1,2,3,…,n(n是城市的数目),再检查d(ij)与d(ik)+d(kj)的值;在此d(ik)与d(kj)分别是目前为止所知道的 i 到 k 与 k 到 j 的最短距离,因此d(ik)+d(kj)就是 i 到 j 经过k的最短距离。所以,若有d(ij)>d(ik)+d(kj),就表示从 i 出发经过 k 再到j的距离要比原来的 i 到 j 距离短,自然把i到j的d(ij)重写为d(ik)+d(kj)<这里就是动态规划中的决策>,每当一个k查完了,d(ij)就是目前的 i 到 j 的最短距离。重复这一过程,最后当查完所有的k时,d(ij)里面存放的就是 i 到 j 之间的最短距离了<这就是动态规划中的记忆化搜索>。利用一个三重循环产生一个存储每个结点最短距离的矩阵.      
      用三个for循环把问题解决了,但是有一个问题需要注意,那就是for循环的嵌套的顺序:我们可能随手就会写出这样的枚举程序,但是仔细考虑的话,会发现是有问题的:
for i:=1 to n do
      for j:=1 to n do
            for k:=1 to n do
                   if…..
      问题出在我们太早的把i-k-j的距离确定下来了,假设一旦找到了i-p-j最短的距离后,i到j就相当处理完了,以后不会在改变了,一旦以后有使i到j的更短的距离时也不能再去更新了,所以结果一定是不对的。所以应当象下面一样来写程序:
for k:=1 to n do
      for i:=1 to n do
            for j:=1 to n do
                  if …..

      这样作的意义在于固定了k,把所有i到j而经过k的距离找出来,然后象开头所提到的那样进行比较和重写,因为k是在最外层的,所以会把所有的i到j都处理完后,才会移动到下一个K。

        for(i=0;i<=m;i++)
        for(j=0;j<=n;j++)
        map[i][j]=INF;//初始化 
		for(k=1;k<=n;k++)//动态规划的思想  
        for(i=1;i<=n;i++)  
        for(j=1;j<=n;j++)  
        {  
            if(i==j)  
            continue;  
            if(map[i][j]>map[i][k]+map[k][j])  
            map[i][j]=map[i][k]+map[k][j];  
        }  

    原文作者:Dijkstra算法
    原文地址: https://blog.csdn.net/yao1373446012/article/details/50773450
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞