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

关于贝尔曼福特算法:

百度百科:贝尔曼-福特算法

------------------------------------------------------------------------------------------------分割线---------------------------------------------------------------------------------------------

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

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

#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 main()
{
    int dis[10],n,m,u[10],v[10],w[10],check,flag;
    //读入顶点个数和边的条数
    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++)
    {check=0;//标记数组dis在本轮松弛中是否会发生更新
        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;
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;
    }
    //1号顶点入队
    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]])//判断是否松弛成功
            {
                dis[v[k]]=dis[u[k]]+w[k];//更新顶点1到顶点v[k]的路程
                if(vis[v[k]]==0)//判断一个顶点是否在队列中,为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


在最短路算法对比:






  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值