CSP-Week7 ProblemC SPFA和负环判定

本文介绍了SPFA(Shortest Path Faster Algorithm)算法,它是对Bellman-Ford算法的一种优化,用于解决带负权边的单源最短路径问题。通过建立队列,每次从队首取出节点松弛其邻接点,以此提高效率。文章还提供了SPFA的具体实现代码,并结合一个具体的实例题目,解释了如何运用SPFA求解最少税费问题,并注意在出现负环时的处理方法。
摘要由CSDN通过智能技术生成

CSP-SPFA和负环判定

基础知识

在讲述SPFA之前,我们需要了解的是他的根源算法Bellman-Ford算法。
Bellman-Ford算法
在一个正权图中,求解单源最短路可以使用Dijkstra算法解决,并且具有较好的时间复杂度O((m+n)logn)。但是在很多实际问题中,会出现很多负权边的情况,一旦出现负边dijkstra算法无法保证原先的正确性,Bellman和Ford引入了新的算法来计算带负权的单源最短路求解算法。
在保证Bellman-Ford算法的正确性之前,需要理解如下的事实:
1、如果图中有n个点,单源最短路径经过的边数<点数
2、如果在路径上边数等于或者超过了n,说明某个点被访问了两次,出现了环路
3、当有一条边(u,v),如果dis[u]是最小值,且(u,v)在dis[v]的路径上,那么可以将dis[v]更新成dis[u]+(u,v)的权值
由此我们可以使用暴力模拟法得出Bellman-Ford算法的求解过程:
因为最短路的边最长为n-1,所以调整n-1次,每次调整过程中遍历所有边,保证第i次调整结束后,长度为i的边最小值已经被确定。时间复杂度为O(nm)
Bellman-Ford算法具体实现代码:

struct edge
{
   
    int x;
    int y;
    int value;
};
edge edges[1000];
for(int i=0;i<=n;i++)
{
   
    dis[i]=inf;
    pre[i]=0;
}
dis[s]=0;
//更新n-1趟,每次更新全部边,并不够优秀
for(int k=1;k<n;k++)
    for(int i=0;i<=m;i++)
    {
   
        if(dis[edges[i].y)>dis[edges[i].x]+edges[i].value)
        {
   
            dis[edges[i].y]=dis[edges[i].x]+edges[i].value;
            pre[edges[i].y]=edges[i].x;
        }

    }

虽然这种方法是可行的,但由于边数m的大小未知,在求解稠密图问题时算法很有可能退化到O(n^3),因此我们需要一种较好的优化方式来修改这种算法。
SPFA队列优化
如果你多读两遍上面的代码并且画出他的具体执行图,就会发现Bellman-Ford的执行过程与BFS十分相似,都是一种在图上的扩散最终遍历到能够到达的所有点。因此我们可以思考能否引入BFS中的队列思想来对Bellman-Ford做以优化。
对Bellman-Ford进行实例测试我们可以发现这样的特点:松弛操作只发生在最短路径的前导节点中,已经松弛过的点上。
可能你听的有些乱,那我们举个例子:
第一次松弛:松弛与源点相连的边,找到所有边长为1的最短路
第二次松弛:第一轮被松弛过点作为前导节点松弛,找到所有边长为2的最短路

可以发现一个特点:只有在前面被松弛过的点后面才会作为先导节点再次进行松弛。
利用这个特点,类比队列,就可以得到SPFA的实现方法:
1、建立一个队列,队列存储要进行松弛的点,初始化将源点放入到队列中
2、每次从队首取点并松弛其邻接点,如果一个邻接点被松弛且队列中无该元素,则将其加入队列
3、在每次松弛结束后进行判断该条路径上的边数,一旦边数>=n,说明重复访问了某个点,出现了负环。对该点打上标记,并且将于该点联通的所有点都打上标记。在进行后续的松弛时,带有负环标记的点不可以被加入队列,否则程序将死循环。
4、重复2-3至队列为空。
SPFA利用了Bellman-Ford算法的松弛性质,在正常数据情况下,大大提高了性能,时间复杂度为 O(km)(k是一个常数)。
SPFA的具体实现:

queue<int> q;
int cnt[N];
int vis[N];
int pre[N];
int dis[N];
void SPFA(int s)
{
   
    while(q.size()) q.pop();
    //q置空
    for(int i=1;i<=n;i++)
    {
    cnt[i]=0,dis[i]=inf,pre[i]=0;vis
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值