Bellman_ford和spfa算法

Bellman_ford算法

  • bellman_ford算法在要求起点到终点存在负权边,要求在指定k步(这是spfa无法替代的)
  • bellman_ford和spfa都可以判断图中有无负权环

🌕实现

image-20230303230814008

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 510,M=10010;
//边结构体
typedef struct{
    int from,to,weight;
}Edge;
//M条边 
Edge edge[M];
int n,m,k;
int dist[N];
//记录上一次被松弛的边的情况
int backup[N];
void bellman_ford(){
    memset(dist,0x3f,sizeof(dist));
    dist[1]=0;
    //这里就类似宽搜的思想
    for(int i=0;i<k;i++){
        //每次迭代都是在上一次的基础上进行的,因此我们在代码实现时要注意保留上一次的结果
        //用上一次松弛过的dist进行本次松弛
        memcpy(backup,dist,sizeof(dist));
        for(int j=0;j<m;j++){
            auto e =edge[j];
            //松弛该边
            // if(dist[e.to]>backup[e.from]+e.weight){
            //     dist[e.to]==backup[e.from]+e.weight;
            // }
            //这里要用上一次已经松弛过的边来扩充下一层
            dist[e.to]=min(dist[e.to],backup[e.from]+e.weight);
        }
    }
}
int main()
{
    cin>>n>>m>>k;
    int from,to,weight;
    for(int i=0;i<m;i++){
        cin>>from>>to>>weight;
        edge[i]={from,to,weight};
    }
    bellman_ford();
    if(dist[n] > 0x3f3f3f3f/2) cout<<"impossible"<<endl;
    else cout<<dist[n]<<endl;
    return 0;
}
  • 可见Bellman_ford算法是通过n次循环来扩充边的,并且每次还是基于上次边更新的成果,且由于它会把之前也已有的边全部更新(导致串联),所以要使用backup数组记录上一次更新的结果

    SPFA

    由于Bellman_ford通过n次循环来扩充边,很想宽搜,所以SPFA就可以采用宽搜来优化

    image-20230303230740168

    #include<iostream>
    #include<cstring>
    #include<algorithm>
    #include<queue>
    //其实spfa很简单,就是把bellman_ford那个傻傻的全面循环来一层层扩充图(很像宽搜)->变成宽搜
    //spfa会长得很像dij 堆版,这里是用队列来实现宽搜
    using namespace std;
    const int N = 1e5+10;
    int n,m;
    int h[N], e[N], w[N], ne[N], idx;
    int q[N], dist[N];
    //st[i],判断i是否已经再队列中了
    bool st[N];
    ​
    void add(int a, int b, int c)  // 添加一条边a->b,边权为c
    {
        e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
    }
    int spfa(){
        memset(dist,0x3f,sizeof dist);
        dist[1]=0;
        queue<int> q;
        q.push(1);
        st[1]=true;
        while(q.size()){
            int t =q.front();
            q.pop();
            st[t]=false;
    ​
            for(int i=h[t];i!=-1;i=ne[i]){
                int j=e[i];
                if(dist[j]>dist[t]+w[i]){
                    dist[j]=dist[t]+w[i];
                    //不在队列中,可能a可以更新c,b也能更新c且b更新c更小,不需要重复往队列中插入
                    if(!st[j]){
                        q.push(j);
                        st[j]=true;
                    }
                }
            }
        }
        return dist[n];
    }
    ​
    int main()
    {
        cin>>n>>m;
        memset(h,-1,sizeof h);
        while(m--){
            int a,b,c;
            cin>>a>>b>>c;
            add(a,b,c);
        }
        int ans=spfa();
        if(ans== 0x3f3f3f3f) puts("impossible");
        else cout<<ans<<endl;
        return 0;
    }
  • SPFA求负环

    n点m边的图,设dist[x]为终点为x,起点任意点的最短距离要经过的边数,如果dist[x]>=n 那么可以推出
    无负权环情况下有n+1->存在负权环

    image-20230303231139054

    #include <cstring>
    #include <iostream>
    #include <algorithm>
    #include <queue>
    ​
    using namespace std;
    ​
    const int N = 2010, M = 10010;
    ​
    int n, m;
    int h[N], w[M], e[M], ne[M], idx;
    //这里dist[i]值得是 起点为任意,终点为i,仅用来更新负权边
    int dist[N], cnt[N];
    bool st[N];
    ​
    void add(int a, int b, int c)
    {
        e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
    }
    ​
    bool spfa()
    {
        queue<int> q;
    ​
        for (int i = 1; i <= n; i ++ )
        {
            st[i] = true;
            q.push(i);
        }
    ​
        while (q.size())
        {
            int t = q.front();
            q.pop();
    ​
            st[t] = false;
    ​
            for (int i = h[t]; i != -1; i = ne[i])
            {
                int j = e[i];
                //更新负权边,能构成负权环的会一致在里面打转,
                //不是负权环的会都出队列,负权环那几个点会进来出去进来
                if (dist[j] > dist[t] + w[i])
                {
                    dist[j] = dist[t] + w[i];
                    cnt[j] = cnt[t] + 1;
    ​
                    if (cnt[j] >= n) return true;
                    if (!st[j])
                    {
                        q.push(j);
                        st[j] = true;
                    }
                }
            }
        }
    ​
        return false;
    }
    ​
    int main()
    {
        scanf("%d%d", &n, &m);
    ​
        memset(h, -1, sizeof h);
    ​
        while (m -- )
        {
            int a, b, c;
            scanf("%d%d%d", &a, &b, &c);
            add(a, b, c);
        }
    ​
        if (spfa()) puts("Yes");
        else puts("No");
    ​
        return 0;
    }
posted @ 2023-03-03 23:15  zhouylove  阅读(19)  评论(0)    收藏  举报