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

来源:互联网 发布:天谕男角色捏脸数据 编辑:程序博客网 时间:2024/05/16 20:48

关于贝尔曼福特算法:

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

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

关于贝尔曼福特算法,假设有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 0x3f3f3fusing 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 0x3f3f3fusing 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 0x3f3f3fusing 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 52 3 21 2 -31 5 54 5 23 4 3输出:0 -3 -1 2 4输入:5 71 2 21 5 102 3 32 5 73 4 44 5 55 3 6输出:0 2 5 9 9


在最短路算法对比:






0 0