ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

图论知识之最短路算法——Bellman-Ford算法和SPFA算法

2022-07-18 08:00:07  阅读:137  来源: 互联网

标签:std dist int SPFA Bellman 算法 return


        今天讲的这两个算法是Bellman-Ford算法和SPFA算法。

        Bellman-Ford算法主要适合求有边数限制的最短路,即路径上最多经过k条边的最短路。该算法效率较低,但代码复杂度小。它的原理是连续进行松弛,在每次松弛时把每条边都更新一下。若在 n-1 次松弛后还能更新,则说明图中有负环,因此无法得出结果,否则就完成。

        Bellman-Ford算法核心代码的具体步骤:

for 1到n
    for 所有边 a,b,w(松弛操作)
       dist[b]=min(dist[b],back[a]+w)

//注意;back[]数组是上一次迭代后dist[]数组的备份。
//由于是每个点同时向外出发,因此需要对dist[]数组进行备份,若不进行备份会因此发生串联反应,影响到下一个点。

  Bellman-Ford的时间复杂度为O(nm),其中n为点数,m为边数。

       完整代码:

#include<iostream>
#include<cstring>

using namespace std;

const int N = 510, M = 10010;

struct Edge {
    int a;
    int b;
    int w;
} e[M];//把每个边保存下来即可
int dist[N];
int back[N];//备份数组防止串联
int n, m, k;//k代表最短路径最多包涵k条边

int bellman_ford() {
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    for (int i = 0; i < k; i++) {//k次循环
        memcpy(back, dist, sizeof dist);
        for (int j = 0; j < m; j++) {//遍历所有边
            int a = e[j].a, b = e[j].b, w = e[j].w;
            dist[b] = min(dist[b], back[a] + w);
            //使用backup:避免给a更新后立马更新b, 这样b一次性最短路径就多了两条边出来
        }
    }
    if (dist[n] > 0x3f3f3f3f / 2) return -1;
    else return dist[n];

}

int main() {
    scanf("%d%d%d", &n, &m, &k);
    for (int i = 0; i < m; i++) {
        int a, b, w;
        scanf("%d%d%d", &a, &b, &w);
        e[i] = {a, b, w};
    }
    int res = bellman_ford();
    if (res == -1) puts("impossible");
    else cout << res;

    return 0;
}

  接着我们来看一下SPFA斯巴发算法。SPFA算法可以说相比Bellman-Ford算法在时间复杂度上有了一个质的突破:它最坏情况下的时间复杂度只能和Bellman-Ford算法持平,是O(nm)。

        其实SPFA算法只是对Bellman-Ford算法的优化,它的中心思想是只遍历那些到源点距离变小的点所连接的边即可,而不是遍历所有边。只有当一个点的前驱结点更新了,该节点才会更新;因此考虑到这一点,我们将创建一个队列每一次加入距离被更新的结点。

        SPFA算法是可以求负权的,但是不可以求带有负权回路(负环)的问题,这一点只有Bellman-Ford算法可以做到。虽然普通的SPFA无法求负权,但我们可以增加一个cnt数组记录每个点到源点的边数,一个点被更新一次就+1,一旦有点的边数达到了n那就证明存在了负环(感觉SPFA有点小小万能??)。

        完整代码:

#include<iostream>
#include<queue>
#include<cstring>
using namespace std;

const int N=1e5+10;

#define fi first
#define se second

typedef pair<int,int> PII;//到源点的距离,下标号

int h[N],e[N],w[N],ne[N],idx=0;
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++;
}

int spfa(){
    queue<PII> q;
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    q.push({0,1});
    st[1]=true;
    while(q.size()){
        PII p=q.front();
        q.pop();
        int t=p.se;
        st[t]=false;//从队列中取出来之后该节点st被标记为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];
                if(!st[j]){//当前已经加入队列的结点,无需再次加入队列,即便发生了更新也只用更新数值即可,重复添加降低效率
                    st[j]=true;
                    q.push({dist[j],j});
                }
            }
        }
    }
    if(dist[n]==0x3f3f3f3f) return -1;
    else return dist[n];
}

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);
    }
    int res=spfa();
    if(res==-1) puts("impossible");
    else printf("%d",res);

    return 0;
}

  其实SPFA也可以利用刚刚提到的cnt数组来判断有没有负环的存在,具体我就不多说了,代码在这里(这里用了STL):

#include <bits/stdc++.h>

typedef long long i64;

const int INF = 0x3f3f3f3f;

int n, m;

bool spfa_better() {
    using edge = std::pair<int ,int>;
    std::cin >> n >> m;
    std::vector<std::vector<edge>> G(n);
    while(m --) {
        int a, b, z;
        std::cin >> a >> b >> z;
        G[a - 1].emplace_back(b - 1, z);
    } 
    // 建图

    std::vector<int> dist(n, 0);
    std::vector<int> pre(n, -1);
    std::vector<bool> inQueue(n, true);
    std::queue<int> queue;
    for (int i = 0; i < n; i++) {
        queue.emplace(i);
    }

    int idx = 0; // 计数器

    auto detectCycle = [&]() {
        std::vector<int> vec;
        std::vector<bool> inStack(n, false);
        std::vector<bool> vis(n, false);
        for (int i = 0; i < n; i++) {
            if (!vis[i]) {
                for (int j = i; j != -1; j = pre[j]) {
                    if (!vis[j]) {
                        vis[j] = true;
                        vec.push_back(j);
                        inStack[j] = true;
                    } else {
                        if (inStack[j]) return true;
                        break;
                    }
                }
                for (int j : vec) inStack[j] = false;
                vec.clear();
            }
        }
        return false;
    };

    while(!queue.empty()) {
        int u = queue.front();
        queue.pop();
        inQueue[u] = false;
        for (auto [v, w] : G[u]) {
            if (dist[u] + w < dist[v]) {
                pre[v] = u;
                dist[v] = dist[u] + w;
                if (++idx == n) {
                    idx = 0;
                    if (detectCycle()) return true;
                }
                if (!inQueue[v]) {
                    queue.push(v);
                    inQueue[v] = true;
                }
            }
        }
    }

    if (detectCycle()) return true;
    return false;
}

int main() {    
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);
    if (spfa_better()) std::cout << "Yes" <<std::endl;
    else std::cout << "No" << std::endl;
    return 0;
}

  下期预告:最短路即将杀青,多源最短路算法也是最最简单的Floyd终于要来了!!

标签:std,dist,int,SPFA,Bellman,算法,return
来源: https://www.cnblogs.com/YZYc/p/Bellman-Ford_SPFA-Class-YPPAH.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有