SPFA

我们都知道一个叫SPFA的算法,它是用来计算单源最短路径的,但是,众所周知它不是很稳定,容易退化。
SPFA是基于什么被提出的?
基于一个叫做Bellman-Ford的算法。

Bellman-Ford

bellman算法实际上比dijkstra有更高的普适性,因为它可以处理有负边权图。但无法处理存在负权回路情况。
算法的时间复杂度为\(O(VE)\),V是顶点数,E是边数。
以下是bellman-ford的伪代码:

#define MAXN 最大点数 
#define MAXM 最大边数
int dis[MAXN],w[MAXM],s;//dis 表示从出发点s到i点的当前最短路径长度,w表示边权值,s表示出发点。
struct lines{//边集结构体
    int u,v;//出发点,到达点
}l[MAXM];
/*
初始化,将dis[s]设为0,dis[其他]设为0x3f3f3f3f,输入边集。
*/
for(int i=1;i<=MAXN;i++){
    for(int j=1;j<=MAXM;j++){
        if(dis[l[j].u]+w[j]<dis[l[j].v]){
            dis[l[j].v]=dis[l[j].u]+w[j];//核心代码
        }
    }
}
//算法结束,dis[i]就是s点到i点的最短路,若dis[i]==0x3f3f3f3f则从s点无法到达该点。

ford算法很好理解,容易看出是一个一维Dp(实际上还有二维dp写法,蒟蒻不会……)。
以下是几个常见不理解的问题:

为什么distance初始化除起点外设成正无穷?

这个……肯定是为了接下来的更新啊!

对于无向图和有向图,这个算法需要变化吗?

说到点上了!对于有向图一定要记住,伪代码中的u,一定是边的入节点!相应的v,一定是出节点!
如果是无向图,我们需要做的就是反过来再执行一次。
也就是原来是这个:

if(dis[l[j].u]+w[j]<dis[l[j].v])dis[l[j].v]=dis[l[j].u]+w[j];

变成了这个:

if(dis[l[j].u]+w[j]<dis[l[j].v])dis[l[j].v]=dis[l[j].u]+w[j];
if(dis[l[j].v]+w[j]<dis[l[j].u])dis[l[j].u]=dis[l[j].v]+w[j];

到底什么是所谓的松弛操作?

松弛操作,很简单,就是这句代码:

if(dis[l[j].u]+w[j]<dis[l[j].v])dis[l[j].v]=dis[l[j].u]+w[j];

惊不惊喜?
原来听了这么久松弛,还以为是啥奇怪的东西,没想到已经用上了!

为什么外层循环要用n次?

这个嘛……实际上仔细想想可以发现,运用蓝白点的思想,一开始起点S是白点,其他都是蓝点。然后由于如果还有蓝点,那么一定还有边连接着蓝点和白点。每次外层循环一定可以遍历到一些这样的边,也一定至少有一个蓝点变成白点
这样的话,我们执行n遍外层循环,一定能保证所有点都求出了最终结果。

为什么说它不适用于负权回路?

负权回路是什么?
负权回路是指,图上一个回路,各边权值之和是负数。
相信看到这里你已经发现,如果图上存在负权回路,那么至少有两点间的最短路会是无限小!
因为可以绕这个回路走无限圈,最短路也跟着无限小……
所以:存在负权回路的图无法求出最短路。
注意是无法!目前已知算法都无法!
bellman-ford算法可以在算法结束时给出错误提示,以判断这张图是否存在负权回路:
方法:在核心代码全部结束后,执行以下代码,若仍存在某条边,使得dis[l[j].u]+w[j]<dis[l[j].v]
那么我们可以判定该图存在负权回路:

for(int i=1;i<=MAXM;i++){
    if(dis[l[j].u]+w[j]<dis[l[j].v])//错误提示。
}

挣扎着优化 ∑( 口 ||

对bellman算法,我们还可以有一个优化,我们看到,bellman算法有的时候会在外层循环没结束的时候就完成了所有松弛操作!
所以我们可以加一个计数:记下每次大循环中松弛的次数,当某次循环不进行松弛时,我们认定算法结束,直接跳出大循环。

#define MAXN 最大点数 
#define MAXM 最大边数
int dis[MAXN],w[MAXM],s;
struct lines{
    int u,v;
}l[MAXM];
int tot;
for(int i=1;i<=MAXN;i++){
    tot=0;//变动
    for(int j=1;j<=MAXM;j++){
        if(dis[l[j].u]+w[j]<dis[l[j].v]){
            tot++;//变动
            dis[l[j].v]=dis[l[j].u]+w[j];
        }
    }
    if(!tot)break;//变动
}

而这个小小优化,有什么作用呢?
70pts -> 100pts
AC记录
一个小操作,可以顺利通过这道题(虽然是弱化版)

劣势

bellman-ford算法的缺点其实刚刚已经体现出来了!
就是,外层循环有大量重复计算!
蒟蒻过几天会写一个SPFA的讲解,SPFA其实就是ford的队列优化。
完结撒花