图论算法之SPFA算法(求带负边但无负环的最短路)

一:算法描述
求单源最短路的SPFA算法,是一种可以处理负权边的算法。对于存在负权边,迪杰斯特拉算法不能使用,但是bellman-ford时间复杂度较高。
简洁起见,我们约定有向加权图G不存在负权回路,即最短路径一定存在。当然,我们可以在执行该算法前做一次拓扑排序,以判断是否存在负权回路。

二:算法基本步骤
几乎所有的最短路径算法都是以下两个步骤:
①初始化
②松弛操作

初始化:
dis数组全部赋值为INF,vis数组标记是否在队列中,一开始队列中没有结点,所有vis数组元素都设置为false。
队列+松弛操作:
读取队头元素,出队并且修改vis数组值为false;将与点u相连的所有点v进行松弛操作,如果能更新估计值(即令d[v]变小),那么就更新,另外,如果点v没有在队列中,那么要将点v入队(记得标记),如果已经在队列中了,那么就不用入队。(因为被松弛过的结点可能会影响到其他结点的dis值,所以要继续入队)

三:判断
判断有无负环:
如果某个点进入队列的次数超过N次则存在负环(SPFA无法处理带负环的图)

和广搜bfs的区别:
    SPFA 在形式上和广度(宽度)优先搜索非常类似,不同的是bfs中一个点出了队列就不可能重新进入队列,但是SPFA中一个点可能在出队列之后再次被放入队列,也就是一个点改进过其它的点之后,过了一段时间可能本身被改进( 重新入队 ),于是再次用来改进其它的点,这样反复迭代下去。

最短路径本身怎么输出?
    在一个图中,我们仅仅知道结点A到结点E的最短路径长度,有时候意义不大。这个图如果是地图的模型的话,在算出最短路径长度后,我们总要说明“怎么走”才算真正解决了问题。如何在计算过程中记录下来最短路径是怎么走的,并在最后将它输出呢?
    我们定义一个path[]数组,path[i]表示源点s到i的最短路程中,结点i之前的结点的编号(父结点),我们在借助结点u对结点v松弛的同时,标记下path[v]=u,记录的工作就完成了。
为什么D算法不适用于存在负边的情况?

为什么不适用呢?其实很容易就可以找到反例。假设一张加权图,有的边长为负数。假设边长最小为-10,我们把所有的边长都加上10,就这就可以得到一张无负值加权图。此时用Dijkstra算法算出一个从节点s到节点t的最短路径L,L共包括n条边,总长为t;那么对于原图,每条边都要减去10,所以原图中L的长度是t-10*n。这是Diskstra算法算出的结果。

那么问题来了:对于加上10之后的图,假设还有一个从s到t的路径M,长度为t1,它共包括n1条边,比L包含的边长多,那么还原回来之后,每条边需要减去10,那么M的总长就是t1-10*n1。那么,是不是M的总长一定比L的总长更长一些呢?不一定。假如n1>n,也就是说M的边数比L的边数更多,那么M减去的要比L减去的更多,那么t1-10*n1

另外,还有一种更简单的例子:假如一张图里有一个总长为负数的环,那么Dijkstra算法有可能会沿着这个环一直绕下去,绕到地老天荒。。。

四。代码

#include
#include 
#include 
#include 
using namespace std;

const int N = 2005;
int nodeNum , edgeNum;
int dis[N]; // d[i]表示源点s到i的距离
int c[N]; // 统计每一个结点入队的次数
bool vis[N];
const int INF = 0x3f3f3f3f;
bool loop; //判断是否是回路
int map[nodeNum][nodeNum];

void spfa(int start)
{
    //建立队列,初始化dis数组和vis数组,把源结点加入到队列中
    queueq;
    memset(vis,false,sizeof(vis));
    for(int i=1;i<=nodeNum;i++)
        dis[i]=INF;
    dis[start]=0;
    q.push(start);
    vis[start]=true;

    //在队列头部取点,然后修改相关数据,并对该边所连接的邻接点进行松弛操作
    //判断相邻的结点vis[],是否在队列中,要是不在,加入队列
    while(!q.empty())
    {
        int temp = q.front();
        q.pop();
        vis[temp] = false;
        for(int i=1;i<=nodeNum;i++)
        {
            if(dis[temp] + map[temp][i] < dis[i])
            {
                dis[i] = dis[temp] + map[temp][i];
                if(!vis[i])
                {
                    q.push(i);
                    vis[i] = true;
                }
            }
        }
    }
}

五:优化策略

1.SLF策略:
若要加入的结点是j,队首结点是i,如果dis[j] < dis[i],则将j插入到队首;否则,插入到队尾。

2.LLL策略
设队首元素为i,队列中所有dis的平均值是x,若dis[i] > x,则将i插入到队尾,查找下一个元素知道找到一个dis[i]<=x,则出队进行松弛操作。


你可能感兴趣的:(图论算法之SPFA算法(求带负边但无负环的最短路))