算法/最短路径/Bellman-Ford贝尔曼福特算法

##问题描述

Dijkstra算法是处理单源最短路径的有效算法,但它局限于边的权值非负的情况,若图中出现权值为负的边,Dijkstra算法就会失效,求出的最短路径就可能是错的。这时候,就需要使用其他的算法来求解最短路径,Bellman-Ford算法就是其中最常用的一个。该算法由美国数学家理查德•贝尔曼 (Richard Bellman, 动态规划的提出者) 和小莱斯特•福特 (Lester Ford) 发明。

算法/最短路径/Bellman-Ford贝尔曼福特算法_第1张图片


##算法分析

首先介绍一下松弛计算:
松弛计算之前如图(a),点B的值是8,但是点A的值加上边上的权重2,得到5,比点B的值小,所以,点B的值减小为5。这个过程的意义是,找到了一条通向B点更短的路线,且该路线是先经过点A,然后通过权重为2的边,到达点B。

如果出现情况(b)则不会修改B的值,因为3+6>8。

算法/最短路径/Bellman-Ford贝尔曼福特算法_第2张图片


  1. 给定图G(V, E) (其中V、E (Edge) 分别为图G的顶点集与边集),源点s,数组 d(i) (Distant) 记录从源点 s 到顶点 i 的路径长度,初始化 d(n)=∞ 表示不可达,d(s)=0;
  2. 执行循环,在循环内部,遍历所有的边,进行松弛计算:
    1. 对于每一条边e(u, v),如果 d(u)+w(u, v) < d(v),则令d(v)=d(u)+w(u, v),w(u, v) 为边 e(u,v) 的权值;
    2. 若上述操作没有对 d 进行更新,说明最短路径已经查找完毕或者部分点不可达,跳出循环。否则执行下次循环;
  3. 遍历途中所有的边 e(u, v),判断是否存在 d(u)+w(u, v) < d(v) 的情况,如果有返回false,否则数组 d(n) 中记录的就是源点s到各顶点的最短路径长度。

之所以需要第三部分,是因为如果存在从源点可达的权为负的回路,则应为无法收敛而导致不能求出最短路径。考虑下面过程,图中存在一条负回路:
经过第一次遍历后如图(b),B的值变为5,C变为8,注意权重为-10的边,它的存在导致A的值变为-2,此时A到B的边有5>5+(-2)。

我之前有一个疑问,为什么情况三说明有一条负回路,而不是有一条负边,现在解决了:C的值8来着5+3,如果CA边的绝对值<8,那么A的值仍为0。假设为CA=-3,因为8+(-3)=5>0(A),所以不会改变A,写出来似乎有点蠢的样子…

算法/最短路径/Bellman-Ford贝尔曼福特算法_第3张图片

第二次遍历后如图©,B的值变为3,C变为6,A变为-4。正是因为有一条负边在回路中,导致每次遍历后,各个点的值不断变小,因此无法收敛。
因为第二部分循环的次数是定长的,而且正常情况下能保证d(v)<=d(u)+w(u, v),所以如果存在无法收敛的情况,则肯定能够在第三部分中检查出来。


代码如下:

package practice4;

import java.util.Scanner;

/**
 * Created by Y on 2017/5/10.
 */
public class BellmanFord {

    private int[] distant;//源点到每一条边的距离
    private Edge[] edge;

    class Edge {
        int u;//边的起点
        int v;//边的终点
        int weight;//边的权重

        Edge(int u, int v, int weight) {
            this.u = u;
            this.v = v;
            this.weight = weight;
        }
    }

    private void relax(int u, int v, int weight) {
        if (distant[v] > distant[u] + weight) {
            distant[v] = distant[u] + weight;
        }
    }

    private void bellmanFord(int nodeNum) {

        distant = new int[nodeNum];

        //初始化源点到其它顶点之间的距离为无穷大
        for (int i = 1; i < nodeNum; i++) {
            distant[i] = Integer.MAX_VALUE;
        }
        distant[0] = 0;

        //进行(nodeNum - 1)次遍历
        for (int i = 1; i < nodeNum; i++) {
            for (Edge anEdge : edge) {
                relax(anEdge.u, anEdge.v, anEdge.weight);
            }
        }

        //判断是否有负回路:存在经过(nodeNum - 1)次遍历后仍可以松弛的边
        boolean flag = true;
        for (Edge anEdge : edge) {
            if (distant[anEdge.v] > distant[anEdge.u] + anEdge.weight) {
                flag = false;
                break;
            }
        }

        //打印结果
        if (flag) {
            System.out.println("没有负环");
            for (int i = 0; i < nodeNum; i ++) {
                System.out.print(distant[i] + " ");
            }
        } else {
            System.out.println("存在负回路,没有最短距离");
        }
    }


    public static void main(String[] args) {

        BellmanFord b = new BellmanFord();

        Scanner in = new Scanner(System.in);
        System.out.println("请输入一个图的顶点总数n和边总数p:");
        int nodeNum = in.nextInt();
        int edgeNum = in.nextInt();
        b.edge = new Edge[edgeNum];

        System.out.println("请输入具体边的数据:");
        for (int i = 0; i < edgeNum; i++) {
            int u = in.nextInt();
            int v = in.nextInt();
            int weight = in.nextInt();
            b.edge[i] = b.new Edge(u, v, weight);
        }

        b.bellmanFord(nodeNum);
    }
}

你可能感兴趣的:(算法导论)