spfa算法求最短路(简化版)

1、最短路径问题介绍

问题解释:
从图中的某个顶点出发到达另外一个顶点的所经过的边的权重和最小的一条路径,称为最短路径

解决问题的算法:
迪杰斯特拉算法(Dijkstra算法)
弗洛伊德算法(Floyd算法)

SPFA算法

之前已经对Dijkstra算法和Floyd算法做了介绍(不懂的可以回去看我之前的题解),所以这篇博客打算对SPFA算法做详细的的介绍。
2、SPFA算法介绍

SPFA算法是求解单源最短路径问题的一种算法,由理查德·贝尔曼(Richard Bellman) 和 莱斯特·福特 创立的。有时候这种算法也被称为 Moore-Bellman-Ford 算法,因为 Edward F. Moore 也为这个算法的发展做出了贡献。它的原理是对图进行V-1次松弛操作,得到所有可能的最短路径。其优于迪科斯彻算法的方面是边的权值可以为负数、实现简单,缺点是时间复杂度过高,高达 O(VE)。但算法可以进行若干种优化,提高了效率。

算法的思路:
我们用数组dis记录每个结点的最短路径估计值,用邻接表或邻接矩阵来存储图G。我们采取的方法是动态逼近法:设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止

我们要知道带有负环的图是没有最短路径的,所以我们在执行算法的时候,要判断图是否带有负环,方法有两种:
开始算法前,调用拓扑排序进行判断(一般不采用,浪费时间)
如果某个点进入队列的次数超过N次则存在负环(N为图的顶点数)

3、SPFA算法手动操作过程

下面我们采用SPFA算法对下图求v1到各个顶点的最短路径,通过手动的方式来模拟SPFA每个步骤的过程
在这里插入图片描述
初始化:

首先我们先初始化数组dis如下图所示:(除了起点赋值为0外,其他顶点的对应的dis的值都赋予无穷大,这样有利于后续的松弛)
这里写图片描述

此时,我们还要把v1如队列:{v1}

现在进入循环,直到队列为空才退出循环。
第一次循环:

首先,队首元素出队列,即是v1出队列,然后,对以v1为弧尾的边对应的弧头顶点进行松弛操作,可以发现v1到v3,v5,v6三个顶点的最短路径变短了,更新dis数组的值,得到如下结果:
这里写图片描述

我们发现v3,v5,v6都被松弛了,而且不在队列中,所以要他们都加入到队列中:{v3,v5,v6}
第二次循环:

此时,队首元素为v3,v3出队列,然后,对以v3为弧尾的边对应的弧头顶点进行松弛操作,可以发现v1到v4的边,经过v3松弛变短了,所以更新dis数组,得到如下结果:
这里写图片描述

此时只有v4对应的值被更新了,而且v4不在队列中,则把它加入到队列中:{v5,v6,v4}
第三次循环:

此时,队首元素为v5,v5出队列,然后,对以v5为弧尾的边对应的弧头顶点进行松弛操作,发现v1到v4和v6的最短路径,经过v5的松弛都变短了,更新dis的数组,得到如下结果:
这里写图片描述

我们发现v4、v6对应的值都被更新了,但是他们都在队列中了,所以不用对队列做任何操作。队列值为:{v6,v4}
第四次循环:
此时,队首元素为v6,v6出队列,然后,对以v6为弧尾的边对应的弧头顶点进行松弛操作,发现v6出度为0,所以我们不用对dis数组做任何操作,其结果和上图一样,队列同样不用做任何操作,它的值为:{v4}
第五次循环
此时,队首元素为v4,v4出队列,然后,对以v4为弧尾的边对应的弧头顶点进行松弛操作,可以发现v1到v6的最短路径,经过v4松弛变短了,所以更新dis数组,得到如下结果:

在这里插入图片描述

因为我修改了v6对应的值,而且v6也不在队列中,所以我们把v6加入队列,{v6}

第六次循环
此时,队首元素为v6,v6出队列,然后,对以v6为弧尾的边对应的弧头顶点进行松弛操作,发现v6出度为0,所以我们不用对dis数组做任何操作,其结果和上图一样,队列同样不用做任何操作。所以此时队列为空。

OK,队列循环结果,此时我们也得到了v1到各个顶点的最短路径的值了,它就是dis数组各个顶点对应的值,如下图:
在这里插入图片描述

4.SPFA算法的简易代码实现

#include <bits/stdc++.h>
#define inf 0x3f3f3f
using namespace std;
int G[101][101],dis[101],N,M,visited[101],count1[101];
queue<int> q;
void spfa(int start)
{
	int i,f;	
	 for(i=1;i<=N;i++)
	  dis[i]=inf; //dis表示源点到该点的最短距离 
	 dis[start]=0;//先把源点入队列 
	 q.push(start);
	 count1[start]++;//count1表示入队列的次数 
	 visited[start]=1; //表示该点是否在队列中 
	 while(!q.empty())//当队列不为空时 
	 {
	 	f=q.front();
	 	q.pop();
	 	visited[f]=0;
	 	for(i=1;i<=N;i++)
	 	 if(dis[f]+G[f][i]<dis[i])//如果队首元素与其他点有距离且加上之后比已知的最短距离还短 
	 	 { dis[i]=dis[f]+G[f][i]; //进行松弛 
	 	   if(!visited[i])//如果不在队列中 
			{ visited[i]=1; //标志存在 
	 	      q.push(i);//入队列 
	 	      count1[i]++;//该点入队列的次数+1 
	 	      if(count1[i]>N)
	 	       { cout<<"存在负权回路!"<<endl;
				  return ; 
	 	       }
			}
	 	 }	
	 }
} 
int main()                            
{  
    cin>>N>>M;
    int i,j,start,end,w;
    for(i=1;i<=N;i++) //初始化无穷 
     for(j=1;j<=N;j++)
      G[i][j]=inf;
    for(i=1;i<=M;i++) //构建邻接矩阵 
    { cin>>start>>end>>w;
      G[start][end]=w; //这是有向图,无向图得再加上返回的路 
    }
    spfa(1);
    for(i=1;i<=N;i++)
     cout<<dis[i]<<" ";
	return 0;
	
} 
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值