Bellman-Ford算法

参考资料:

最短路径算法—Bellman-Ford(贝尔曼-福特)算法分析与实现(C/C++)
Bellman-Ford算法详讲

算法推导建议参考第一篇文章,讲的很详细。

解析(摘自参考资料):

Dijkstra算法是处理单源最短路径的有效算法,但它局限于边的权值非负的情况,若图中出现权值为负的边,Dijkstra算法就会失效,求出的最短路径就可能是错的。

这时候,就需要使用其他的算法来求解最短路径,Bellman-Ford算法就是其中最常用的一个。该算法由美国数学家理查德•贝尔曼(Richard Bellman, 动态规划的提出者)和小莱斯特•福特(Lester Ford)发明。

适用条件&范围:

单源最短路径(从源点s到其它所有顶点v);

有向图&无向图(无向图可以看作(u,v),(v,u)同属于边集E的有向图);

边权可正可负(如有负权回路输出错误提示);

差分约束系统;

Bellman-Ford算法的流程如下:

给定图G(V, E)(其中V、E分别为图G的顶点集与边集),源点s,数组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算法寻找单源最短路径的时间复杂度为O(V*E).

Bellman-Ford算法可以大致分为三个部分

  • 第一,初始化所有点。每一个点保存一个值,表示从原点到达这个点的距离,将原点的值设为0,其它的点的值设为无穷大(表示不可达)。

  • 第二,进行循环,循环下标为从1到n-1(n等于图中点的个数)。在循环内部,遍历所有的边,进行松弛计算。

  • 第三,遍历途中所有的边(edge(u,v)),判断是否存在这样情况:
    d(v) > d (u) + w(u,v)
    则返回false,表示途中存在从源点可达的权为负的回路。

代码:

//
//  main.cpp
//  Bellman-Ford
//
//  Created by wasdns on 16/11/20.
//  Copyright © 2016年 wasdns. All rights reserved.
//

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstdlib>
using namespace std;

#define maxn 100000001;                 //无穷大

struct Edge                             //存储边的结构体
{
    int u;
    int v;
    int weight;
};

Edge edge[10001];                       //存储边的数组

int Fordlen[10001];                     //源点到各个点的最短路径长度

/*
 用于初始化Fordlen数组的函数
    注意:源点到自身最短路径长度为0。
 */

void Initial(int n, int source)
{
    for (int i = 1; i <= n; i++)
    {
        Fordlen[i] = maxn;
    }
    
    Fordlen[source] = 0;
}

/*
 用于输入边,初始化图的函数
 由两部分组成:
        (1)调用Initial函数初始化;
        (2)输入边,同时维护Fordlen数组
 */

void CreatGraph(int n, int edgenum, int source)
{
    Initial(n, source);
    
    for (int i = 1; i <= edgenum; i++)
    {
        cin >> edge[i].u >> edge[i].v >> edge[i].weight;
        
        if (edge[i].u == source) {
            Fordlen[edge[i].v] = edge[i].weight;
        }
        
        if (edge[i].v == source) {
            Fordlen[edge[i].u] = edge[i].weight;
        }
    }
}

/*
 松弛计算函数
 */

void relax_cal(int u, int v, int weight)
{
    if (Fordlen[u] + weight < Fordlen[v]) {
        Fordlen[v] = Fordlen[u] + weight;
    }
}

/*
 Bellman-Ford算法主体:
    (1)初始化、建图;
    (2)松弛计算,维护Fordlen数组;
    (3)判断是否有负权环,有的话返回false。
 */

bool Alg_Bellman_Ford(int n, int edgenum, int source)
{
    CreatGraph(n, edgenum, source); //(1)初始化
    
    for (int i = 1; i <= n-1; i++)  //(2)循环最多执行n-1次,每一次遍历所有边
    {
        for (int j = 1; j <= edgenum; j++)
        {
            relax_cal(edge[j].u, edge[j].v, edge[j].weight);
        }
    }
    
    bool flag = true;               //(3)判断是否有负权边
    
    for (int i = 1; i <= edgenum; i++)
    {
        if (Fordlen[edge[i].u] + edge[i].weight < Fordlen[edge[i].v]) {
            
            flag = false;
            
            break;
        }
    }
    
    return flag;
}

/*
 输出函数:
    (1)如果没有负权环,输出Fordlen数组
    (2)有负权环,输出"The graph has negative circle."
 */

void Print(bool flag, int n)
{
    if (flag)
    {
        for (int i = 1; i <= n; i++)
        {
            cout << Fordlen[i] << endl;
        }
    }
    
    else cout << "The graph has negative circle." << endl;
}

int main()
{
    int n, edgenum, source;
    
    cin >> n >> edgenum >> source;
    
    bool flag = Alg_Bellman_Ford(n, edgenum, source);
    
    Print(flag, n);
    
    return 0;
}

测试:

一:

4 6 1
1 2 20
1 3 5
4 1 -200
2 4 4
4 2 4
3 4 2

结果:

The graph has negative circle.

二:

4 6 1
1 2 2
1 3 5
4 1 10
2 4 4
4 2 4
3 4 2

结果:

0
2
5
6

2016/11/20

posted @ 2016-11-20 23:58  Wasdns  阅读(342)  评论(0编辑  收藏  举报