Bellman_ford 和 SPFA

求单源最短路时,当存在负权边时,使用Bellman_ford 和 SPFA算法

对于存在负权回路需要进行判断。当有边数限制时,即使存在负权回路也是可以解的,但是没有边数限制那么就无解,答案为负无穷大。

Bellman_ford

算法流程:( n 为点数,m 为边数)(O(n*m))

  1. 循环 n 次
  2.  n 次循环中,循环边数 m 次,找最短边

对于有边数限制,需要一个备份数组存储上一次的路径,避免发生路径串联(会在代码中体现)

SPFA

全称:Shortest Path Fast Algorithm

给定一张有向图,若对于图中的某一条边(x,y,z),有 dist[y] <= dist[x] + z 成立(dist表示最短路径),则称该边满足三角形不等式;若所有的边都满足三角形不等式,则 dist 数组就是所求的最短路。

SPFA算法流程:

  1. 建立一个队列,最初队列中只有起点1
  2. 取出队头元素,扫描它的所有出边(x,y,z),若不满足三角形不等式,则更新 dist[y],同时若 y 不在队列中,则将 y 入队
  3. 重复操作,直至队列为空

SPFA算法是 Bellman_ford 的优化,在任意时刻,队列中都保存了待扩展的点(也就是说队列中保存的点都以及完成了更新,使其满足三角形不等式,设该点为 y ,那么 dist[y] 就是起点到 y 的最短路;然后使用这个最短的特性去更新其他不满足三角形不等式的点。这就是和dij不同的地方,dij是使用一个小根堆,每次取出的点是最短路),每次入队相当于完成了一次 dist 数组更新操作,使其满足三角形不等式。一个点可能多次入队、出队。最终所有节点收敛到全部满足三角形不等式。这个队列避免了 Bellman_ford 算法中对不需要扩展的点的冗余扫描。时间复杂度是O(m),但是在特殊构造的图中,可能退化O(n*m)

Bellman_ford代码实现

#include<iostream>
#include<cstring>

using namespace std;

const int N = 510, M = 10010, INF = 0x3f3f3f3f;

int n, m, k;
struct edge
{
    int a, b, w;
}edges[M];
int dist[N], backup[N];

void bellman()
{
    memset(dist, 0x3f, sizeof dist);  // 由于有边数限制,所以需要备份数组,避免路径串联
    dist[1] = 0;
    for(int i = 0; i < k; i++)  // 这有路径限制 k 条
    {
        memcpy(backup, dist, sizeof backup);
        
        for(int j = 1; j <= m; j++)
        {
            int a = edges[j].a, b = edges[j].b, w = edges[j].w;
            dist[b] = min(dist[b], backup[a] + w);
        }
    }
    // 当存在负权回路时,当起点到不了 n 时,dist[n] = INF,但是如果 n-1 点到 n 点的权为负数,那么 dist[n] 会小于INF,所有当 dsit[n] 大于一个比较大的数时,就不可达
    if(dist[n] > INF / 2) cout << "impossible" << endl;
    else cout << dist[n] << endl;
}

SPFA代码实现

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N = 1e5 + 10;
int h[N], e[N], w[N], ne[N], idx;
int dist[N];
bool st[N];  // 记录点是否在队列中
int n , m;
void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

void spfa()
{
    memset(dist, 0x3f, sizeof dist);
    queue<int> qu;
    dist[1] = 0;
    st[1] = true;
    qu.push(1);
    while(!qu.empty())
    {
        int t = qu.front(); qu.pop(); st[t] = false;
        for(int i = h[t]; ~i; i = ne[i])
        {
            int j = e[i];
            if(dist[j] > dist[t] + w[i])
            {
                dist[j] = dist[t] + w[i];
                if(!st[j])
                {
                    qu.push(j);  // 当该点满足三角形不等式,且不在队列中;将其入队,去扩展其他不满足的点
                    st[j] = true;
                }
            }
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值