总结一下最短路径的贝尔曼-福特算法(Bellman-Ford)及用队列优化(spfa)

关于贝尔曼福特算法,假设有n个顶点,我们只需要遍历n-1轮就可以了,因为在一个含n个顶点的图中,任意两点之间的最短路径最多含有n-1条边, 什么原理,我就不讲了,网上大牛博客很多,我在这里上一点干货:

1.最原始的贝尔曼福特算法,时间复杂度为O(NM):

再次学习

//板子
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <stack>
#include <algorithm>
using namespace std;
const int inf=0x3f3f3f3f;
const int N=5005;
int n,m;
struct node
{
    int w;
    int v;
    int nex;
} e[N];
int firs[N],tot;
void edge(int u,int v,int w)
{
    e[tot].w=w;
    e[tot].v=v;
    e[tot].nex=firs[u];
    firs[u]=tot++;
}
int dis[N],vis[N];
void spfa(int u)
{
    memset(dis,inf,sizeof(dis));
    dis[u]=0;
    memset(vis,0,sizeof(vis));
    vis[u]=1;
    queue<int>q;
    q.push(u);
    while(!q.empty())
    {
        int p=q.front();
        q.pop();
        vis[p]=0;
        for(int i=firs[p]; i!=-1; i=e[i].nex)
        {
            if(dis[p]+e[i].w<dis[e[i].v])
            {
                dis[e[i].v]=dis[p]+e[i].w;
                if(!vis[e[i].v])
                {
                    q.push(e[i].v);
                    vis[e[i].v]=1;
                }
            }
        }
    }
}
int main()
{
    while(~scanf("%d%d",&m,&n))
    {
        tot=0;
        memset(firs,-1,sizeof(firs));
        for(int i=0; i<m; i++)
        {
            int x,y,w;
            scanf("%d%d%d",&x,&y,&w);
            edge(x,y,w);
            edge(y,x,w);
        }
        spfa(1);
        printf("%d\n",dis[n]);
    }
    return 0;
}

外层进行n-1次循环,内层进行m次循环,内层每进行一次循环,至少找出来一条边能走通,而图有n个点,所以进行n-1就够了。
经过我数据的实验,它只能检查出来负权边,而不能避免负权边(就是每进行一次循环距离就会减少,我也不知道怎么描述QAQ)。

#include <stdio.h> 
#include <string.h> 
#include <string> 
#include <iostream> 
#include <stack> 
#include <queue> 
#include <vector> 
#include <algorithm> 
#define mem(a,b) memset(a,b,sizeof(a)) 
#define maxnum 3010 
#define inf 0x3f3f3f 
using namespace std;  
int main()  
{  
    int dis[10],n,m,u[10],v[10],w[10];  
    //读入顶点个数和边的条数 
    scanf("%d%d",&n,&m);  
    //读入边 
    for(int i=1; i<=m; i++)  
        scanf("%d%d%d",&u[i],&v[i],&w[i]);  
    //初始化dis数组 
    for(int i=1; i<=n; i++)  
        dis[i]=inf;  
    dis[1]=0;  
    //贝尔曼-福特算法(Bellman-Ford)的核心语句 
    for(int k=1; k<=n-1; k++)  
        for(int i=1; i<=m; i++)  
            if(dis[u[i]]+w[i]<dis[v[i]])  
                dis[v[i]]=dis[u[i]]+w[i];  
    //输出结果 
    for(int i=1;i<=n;i++)  
        printf("%d ",dis[i]);  
}  

这个算法还可以用来检测一个图是否含有负权回路,如果进行了n-1轮松弛操作后仍然存在:

if(dis[u[i]]+w[i]<dis[v[i]])  
                dis[v[i]]=dis[u[i]]+w[i]; 

如果这种情况持续存在,那么这个图一定含有负权回路。
有时候在n-1轮松弛之前就已经算出了最短路,这时候我们可以判断一下来进行优化:

#include <stdio.h> 
#include <string.h> 
#include <string> 
#include <iostream> 
#include <stack> 
#include <queue> 
#include <vector> 
#include <algorithm> 
#define mem(a,b) memset(a,b,sizeof(a)) 
#define maxnum 3010 
#define inf 0x3f3f3f 
using namespace std;//开四个数组 
int dis[10],n,m,u[10],v[10],w[10],check,flag;
int main()  
{    
    //读入顶点个数和边的条数 
    scanf("%d%d",&n,&m);  
    //第一步,读入边 
    for(int i=1; i<=m; i++)  
        scanf("%d%d%d",&u[i],&v[i],&w[i]);  
    //第二步,初始化dis数组 
    for(int i=1; i<=n; i++)  
        dis[i]=inf;  
    dis[1]=0;  
    //第三步,贝尔曼-福特算法(Bellman-Ford)的核心语句 
    for(int k=1; k<=n-1; k++)  
    {  
        for(int i=1; i<=m; i++)  
            if(dis[u[i]]+w[i]<dis[v[i]])  
            {  
                dis[v[i]]=dis[u[i]]+w[i];  
                check=1;//数组dis发生更新,改变check的值 
            }  
        //松弛完毕后检测dis是否有更新 
        if(check==0)break;//如果没有更新,提前退出循环 
    }  
    //检测负权回路并输出结果 
    flag=0;  
    for(int i=1; i<=m; i++)  
        if(dis[u[i]]+w[i]<dis[v[i]])  
            flag=1;  
    if(flag==1)  
        printf("此图含有负权回路\n");  
    else  
    {  
        for(int i=1; i<=n; i++)  
            printf("%d ",dis[i]);  
    }  
}  

2.贝尔曼福特算法的队列优化,时间复杂度为O(N):

#include <stdio.h> 
#include <string.h> 
#include <string> 
#include <iostream> 
#include <stack> 
#include <queue> 
#include <vector> 
#include <algorithm> 
#define mem(a,b) memset(a,b,sizeof(a)) 
#define maxnum 3010 
#define inf 0x3f3f3f 
using namespace std;  //开7个数组
int dis[maxnum],n,m,u[maxnum],v[maxnum],w[maxnum];  
int first[maxnum],next[maxnum],vis[maxnum];  
int main()  
{  
    queue<int>q;  
    //读入顶点个数和边的条数 
    scanf("%d%d",&n,&m); 
    //首先三个初始化 
    //第一步,初始化dis数组 
    for(int i=1; i<=n; i++)  
        dis[i]=inf;  
    dis[1]=0;  
    //第二步,初始化vis 
    for(int i=1; i<=n; i++)  
        vis[i]=0;          
    //第三步,初始化first的下标为-1,表示1~n号顶点暂时都没有边 
    for(int i=1; i<=n; i++)  
        first[i]=-1;  
    //第四步,读入边并建立邻接表 
    for(int i=1; i<=m; i++)  
    {  
        scanf("%d%d%d",&u[i],&v[i],&w[i]);  
        next[i]=first[u[i]];  
        first[u[i]]=i;  
    }
    //第五步,起始顶点入队 
    vis[1]=1;
    q.push(1);  
    while(!q.empty())  
    {  
    //第六步,下一个点开始
        int k=first[q.front()];  
        while(k!=-1)//扫描当前顶点所有的边 
        { 
            if(dis[u[k]]+w[k]<dis[v[k]])//第7步,判断是否松弛成功 
            {  
                dis[v[k]]=dis[u[k]]+w[k];//更新顶点1到顶点v[k]的路程 
                if(vis[v[k]]==0)//第8步,判断一个顶点是否在队列中,为0表示不在队列,加入队列 
                {  
                    q.push(v[k]);  
                    vis[v[k]]=1;//同时标记顶点v[k]已经入队 
                }  
            }  
            k=next[k];  
        }  
        vis[q.front()]=0;  
        q.pop();  
    }  
    //输出结果 
    for(int i=1; i<=n; i++)  
        printf("%d ",dis[i]);  
    return 0;  
}  

对于上面的代码,给出两组样例:

输入:  
5 5  
2 3 2  
1 2 -3  
1 5 5  
4 5 2  
3 4 3  
输出:  
0 -3 -1 2 4  

输入:  
5 7  
1 2 2  
1 5 10  
2 3 3  
2 5 7  
3 4 4  
4 5 5  
5 3 6  
输出:  
0 2 5 9 9 
    原文作者:Bellman - ford算法
    原文地址: https://blog.csdn.net/zxy160/article/details/54584546
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞