最短路——Bellman-Ford算法

最短路——Bellman-Ford算法

Dijkstra算法是解决最短路问题最有效的算法了,但它限于权值非负的情况,若图中出现权值为负的边,Dijkstra算法就会失效,求出的最短路径就可能是错的。
Bellman-Ford算法可以对回路中是否有负权值进行判断,用于解决存在权值为负的最短路问题。
适用条件&范围:
单源最短路径(从源点s到其它所有顶点v);
有向图&无向图(无向图可以看作(u,v),(v,u)同属于边集E的有向图);
边权可正可负(如有负权回路输出错误提示);
差分约束系统;
算法流程:
①数组Distant[i]记录从源点s到顶点i的路径长度,初始化数组Distant[n]为, Distant[s]为0;
②以下操作循环执行至多n-1次,n为顶点数:
对于每一条边e(u, v),如果Distant[u] + w(u, v) < Distant[v],则另Distant[v] = Distant[u]+w(u, v)。w(u, v)为边e(u,v)的权值;
若上述操作没有对Distant进行更新,说明最短路径已经查找完毕,或者部分点不可达,跳出循环。否则执行下次循环;
③为了检测图中是否存在负环路,即权值之和小于0的环路。对于每一条边e(u, v),如果存在Distant[u] + w(u, v) < Distant[v]的边,则图中存在负环路,即是说改图无法求出单源最短路径。否则数组Distant[n]中记录的就是源点s到各顶点的最短路径长度。
Bellman-Ford算法可以大致分为三个部分
①初始化所有点。每一个点保存一个值,表示从原点到达这个点的距离,将原点的值设为0,其它的点的值设为无穷大(表示不可达)。
②进行循环,循环下标为从1到n-1(n等于图中点的个数)。在循环内部,遍历所有的边,进行松弛计算。
③遍历途中所有的边(edge(u,v)),判断是否存在这样情况:
d(v) > d (u) + w(u,v),则返回false,表示途中存在从源点可达的权为负的回路。
IMPORTANT!!!
之所以需要第三部分的原因,是因为,如果存在从源点可达的权为负的回路。则 应为无法收敛而导致不能求出最短路径。
考虑如下的图:
《最短路——Bellman-Ford算法》

经过第一次遍历后,点B的值变为5,点C的值变为8,这时,注意权重为-10的边,这条边的存在,导致点A的值变为-2。(8+ -10=-2)
《最短路——Bellman-Ford算法》
第二次遍历后,点B的值变为3,点C变为6,点A变为-4。正是因为有一条负边在回路中,导致每次遍历后,各个点的值不断变小。

在回过来看一下bellman-ford算法的第三部分,遍历所有边,检查是否存在d(v) > d (u) + w(u,v)。因为第二部分循环的次数是定长的,所以如果存在无法收敛的情况,则肯定能够在第三部分中检查出来。比如
《最短路——Bellman-Ford算法》
此时,点A的值为-2,点B的值为5,边AB的权重为5,5 > -2 + 5. 检查出来这条边没有收敛。

所以,Bellman-Ford算法可以解决图中有权为负数的边的单源最短路径问。

代码实现

#include <cstdio>
#include <iostream>
using namespace std;
#define N 0x3f3f3f3f
int dis[110];                 //dis数组存的是起点到每个点的长度
int n,m,s;                    //n个点,m条边,s是源点
typedef struct edge
{
    int u,v;
    int dis;
} edge;                       //u,v是边的两个点,dis是每条边的权值
edge e[110];

void init()                   //初始化
{
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1; i<=n; i++)
        dis[i]=N;
    dis[s]=0;
    for(int i=1; i<=m; i++)
    {
        cin>>e[i].u>>e[i].v>>e[i].dis;
        if(e[i].u==s)                   //设置初始情况
            dis[e[i].v]==e[i].dis;
    }
}

bool bellman_ford()
{
    for(int i=1; i<=n-1; i++)
    {
        for(int j=1; j<=m; j++)
        {
            if(dis[e[j].v]>dis[e[j].u]+e[j].dis)
                dis[e[j].v]=dis[e[j].u]+e[j].dis;    //松弛计算
        }
    }
    bool flag=1;
    for(int i=1; i<=m; i++)                   //判断是否有负环路
    {
        if(dis[e[i].v]>dis[e[i].u]+e[i].dis)
        {
            flag=0;
            break;
        }
    }
    return flag;
}

int main()
{
    init();
    if(bellman_ford())
    {
        for(int i=1; i<=n; i++)
            cout<<dis[i]<<endl;
    }
    return 0;
}

测试数据
5 10 1
1 2 6
1 5 7
2 3 5
3 2 -2
2 5 8
2 4 -4
4 3 7
5 3 -3
5 4 9
4 1 2
结果:
0
2
4
-2
7
《最短路——Bellman-Ford算法》
补充:
考虑:为什么要循环n-1次?
答:因为最短路径肯定是个简单路径,不可能包含回路的,
如果包含回路,且回路的权值和为正的,那么去掉这个回路,可以得到更短的路径
如果回路的权值是负的,那么肯定没有解了
图有n个点,又不能有回路
所以最短路径最多n-1边
又因为每次循环,至少relax一边
所以最多n-1次就行了

    原文作者:Bellman - ford算法
    原文地址: https://blog.csdn.net/ttooop/article/details/54838738
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞