Bellman ford和SPFA算法全解析,从动态规划到队列贪心

Posted C_YCBX Py_YYDS

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Bellman ford和SPFA算法全解析,从动态规划到队列贪心相关的知识,希望对你有一定的参考价值。

*关于Bellman ford和SPFA算法的详解

我是白嫖的leetcode会员,然后看了关于图单源最短路径的讲解,讲解的非常好(虽然没代码演示,但基本上一看思路就有了)。

适用性分析(先看视频)

Blellman ford算法

  • DP方法:以 dp[i][j] 表示选择最多 i 条边,从起点到 j 的最短距离。每次的更新依赖于上一行 dp[i-1][j] 的答案,故可滚动数组优化为一维数组。时间复杂度O(N^3)
  • 多次遍历边的更新方法:提前记录好哪两个结点有边,每进行一次整个边的遍历,就相当于完成了最多选择一条边到达目的地的最短距离的效果。平均时间复杂度 O(N*V)(V是边的个数,极端情况下会掉到 O(N*N*V)的复杂度,因为最多是可以进行 N-1 次循环的)

很明显无论是哪种方式实现,最终都是依赖选择多少条边的结果,所以该算法适用于指定最多经过k条边的最短路径题目

SPFA算法

  • 这个算法只是Bellman ford算法的再优化,使得每次选择的边的关系达到最优,大大减少了边的遍历次数,时间复杂度较为稳定(相对Bellmanford稳定很多)的在 O(N*V)

这个原本也是基于Bellmanford算法优化的,除了无法表示最多经过k条边,其余效率比之前的算法更快,所以适用于求存在负权值的单源最短路径问题,而无法精确为最多经过了多少条边。

以题代讲

蓝桥杯–最短路

在这里插入图片描述

Bellman ford的动态规划解决(超时,过三个)

在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
#define LL long long
int n,m;
vector<int>dp(20001,INT_MAX/2);
map<int,map<int,int> > MAP;
LL read() {
    LL res = 0;
    bool f = 1;
    char c;
//先耗掉一个getchar来进行判断符号
   c = getchar();
   if(c == '-')f = 0;
    else res+= (c-'0');

    while (isdigit(c = getchar())) {
        res = (LL)res * 10 + (c-'0');
    }

    if (f)
        return res;

    return res*-1;
}
int main(){
    n = read();
    m = read();
    for(int i=0;i<m;i++){
        int a =read(),c = read(),len = read();
        MAP[a][c] = len;
        if(a == 1)
            dp[c] = len;
    }
    dp[1] = 0;
    vector<int>pre = dp;
    //外层循环经过最多i条路到达该结点的最短距离,最多经过n-1条
    //里面几层都是用于更新没一行的数据
    for(int i=2;i<=n-1;i++){
        for(int j=2;j<=n;j++){
            for(int k = 1;k<=n;k++){
                int t = MAP[k][j];
                if(t)
                dp[j] = min(dp[j],pre[k]+t);
            }
        }
        pre = dp;
    }
    for(int i=2;i<=n;i++){
        cout<<dp[i]<<endl;
    }
}

Bellman ford按边遍历解决(速度竟比SPFA快,我惊了)

在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
#define LL long long
int n,m;
//以边为单位遍历更新
struct pos{
    int i;
    int j;
    int len;
};
vector<int>dp(20001,INT_MAX/2);
LL read() {
    LL res = 0;
    bool f = 1;
    char c;
//先耗掉一个getchar来进行判断符号
   c = getchar();
   if(c == '-')f = 0;
    else res+= (c-'0');

    while (isdigit(c = getchar())) {
        res = (LL)res * 10 + (c-'0');
    }

    if (f)
        return res;

    return res*-1;
}
int main(){
    n = read();
    m = read();
    //记录m条边的关系
    pos MAP[m];
    for(int i=0;i<m;i++){
        int a =read(),c = read(),len = read();
        MAP[i] = {a,c,len};
    }
    dp[1] = 0;
    //外面一层代表遍历边的次数,最多为n-1次
    for(int i=1;i<=n-1;i++){
			bool flag = true;
        for(int k=0;k<m;k++){
            if(dp[MAP[k].j]>dp[MAP[k].i]+MAP[k].len){
                dp[MAP[k].j] = dp[MAP[k].i]+MAP[k].len;
                flag = false;
            }
        }
        //一旦有一轮遍历未更新一次,则弹出循环,得出答案
        if(flag)
            break;
    }
    for(int i=2;i<=n;i++){
        cout<<dp[i]<<endl;
    }
}

最终优化–SPFA算法

毕竟SPFA的全称为Shortest Path Faster Algorithm,也得当担得起这个名字啊🤣

在这里插入图片描述

主要因为用STL容器存储数据的原因,所以似乎稍慢。

#include<bits/stdc++.h>
using namespace std;
#define LL long long
int n,m;
//以边为单位遍历更新,再进一步优化便得到得到SPFA算法
//我们需要构造一个以任一点为起点的,它所连接的通路的结构,以方便队列进行操作,用哈希表进行映射最好
map<int,vector<pair<int,int> > >MAP;
vector<int>dp(20001,INT_MAX/2);
queue<int>Q;
//标记结点是否在队列之中
bool check[20001] = {false};
LL read() {
    LL res = 0;
    bool f = 1;
    char c;
//先耗掉一个getchar来进行判断符号
   c = getchar();
   if(c == '-')f = 0;
    else res+= (c-'0');

    while (isdigit(c = getchar())) {
        res = (LL)res * 10 + (c-'0');
    }

    if (f)
        return res;

    return res*-1;
}
int main(){
    n = read();
    m = read();
    for(int i=0;i<m;i++){
        int a =read(),c = read(),len = read();
        MAP[a].push_back(make_pair(c,len));
    }
    dp[1] = 0;
    Q.push(1);
    check[1] = true;
    while(!Q.empty()){
        int node = Q.front();Q.pop();check[node] = false;
        vector<pair<int,int> >& t = MAP[node];
        //以node为起点开始更新,一旦被更新且队中无该结点,则入队。
        for(int i=0;i<t.size();i++) {
            if(dp[t[i].first]>dp[node]+t[i].second){
                dp[t[i].first] = dp[node]+t[i].second;
                //入队操作
                if(!check[t[i].first])
                    Q.push(t[i].first);
                    check[t[i].first] = true;
            }
        }
    }


    for(int i=2;i<=n;i++){
        cout<<dp[i]<<endl;
    }
}

以上是关于Bellman ford和SPFA算法全解析,从动态规划到队列贪心的主要内容,如果未能解决你的问题,请参考以下文章

Bellman_Ford和SPFA:带负边权的单源最短路算法

关于Dijkstra、SPFA、Bellman-Ford、Floyed算法的问题

数据结构-图的遍历之Bellman-Ford算法和SPFA算法

ACwing(基础) --- Bellman-Ford&SPFA

四大算法解决最短路径问题(Dijkstra+Bellman-ford+SPFA+Floyd)

Bellman-ford算法SPFA算法求解最短路模板