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

来源:互联网 发布:大学生淘宝兼职 编辑:程序博客网 时间:2024/05/16 02:06

问题描述

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

这里写图片描述


算法分析

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

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

这里写图片描述


  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,写出来似乎有点蠢的样子…

这里写图片描述

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


代码如下:

import java.util.Scanner;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;        }        //  进行(nodeNum - 1)次遍历        for (int i = 1; i < nodeNum; i++) {  //每一个点            for (Edge anEdge : edge) {  //每一条边                relax(anEdge.u, anEdge.v, anEdge.weight);            }        }        //  判断是否有负回路        boolean flag = true;        for (Edge anEdge : edge) {            if (distant[anEdge.u] > distant[anEdge.v] + anEdge.weight) {                flag = false;                break;            }        }        //  打印结果        if (flag) {            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);    }}
0 0
原创粉丝点击