图论算法之最短路径(DijkstraFloydBellman-fordSPFA

1、图论最短路径概述

图论算法为了求解一个顶点到另一个顶点的最短路径,即如果从图中某一顶点(称为源点)到达另一顶点(称为终点)的路径可能不止一条,如何找到一条路径,使得沿此路径各边上的权值总和(即从源点到终点的距离)达到最小,这条路径称为最短路径(shortestpath)。

最短路径有很多特殊的情况,包括有向图还是无向图,有没有负权边等。通常解决图论中最短路径问题的算法有DijkstraFloydBellman-fordSPFA四种算法,下面逐一介绍各个算法的思想、流程和Java代码实现。

2、Dijkstra算法

迪杰斯特拉算法主要特点是以起始点为中心向外层扩展,直到扩展到终点为止。迪杰斯特拉算法采用的是贪心策略,将Graph中的节点集分为最短路径计算完成的节点集S和未计算完成的节点集T,每次将从T中挑选V0->Vt最小的节点Vt加入S,并更新V0经由VtT中剩余节点的更短距离,直到T中的节点全部加入S中,它贪心就贪心在每次都选择一个距离源点最近的节点加入最短路径节点集合。迪杰斯特拉算法只支持非负权图,它计算的是单源最短路径,即单个源点到剩余节点的最短路径,时间复杂度为O(n²),如果求解整个图各个节点之间的最短路径,那么使用迪杰斯特拉算法的时间复杂度就是O(n³)

如果有权重是负值,则无法保证已经计算好的最短路径的节点构成最短路径,负数会影响结果。

Dijkstra算法适用场景:

一般算法书中都说适合于有向无环图(DAG)。但是并不代表无向图就不行,只要将源点和目标点的权重相互之间赋值成一个值即可。如果是有环图的话,对于每个节点都是做了一层遍历,也不会出现死循环和重复计算的问题,因此迪杰斯特拉算法是可以用在无向图和有环图中的,适合于求单源最短路径。如果想适用于多源最短路径,就要将每个节点都进行遍历,那么时间复杂度就是O(n³)

把顶点的集合V分成两组:

  • S:{已经求出最短路径的顶点}
  • T=V-S:{尚未确定最短路径的顶点}

保证两点:

①每一个顶点对应一个距离值

S中顶点:从V0到此顶点的最短路径长度

T中顶点:从V0到此顶点的只包括S中顶点作中间顶点的最短路径长度

S中各个顶点的距离值<=T中各个顶点的距离值。

将T中顶点按照最短路径递增的次序加入到S中,即每次从T中找出距离值最小的顶点加入到S中,直到S=V为止。

算法步骤:

  • 初始时,令S={V0}T={其余顶点},T中顶点Vi对应的距离值,有:

    • 若存在<V0,Vi>,为<V0,Vi>弧上的权值;
    • 若不存在<V0,Vi>,为无穷大.
  • T中选取一个其距离值为最小的顶点W,加入S,则S={V0, W}
  • 对T中其余顶点的距离值进行修改:若加进W作中间顶点,从V0Vi的距离值比不加W的路径要短,则修改此距离值;
  • 重复上述步骤,直到S中包含所有顶点,即S=V为止。

代码实现:

public class DijstraAlgorithm {//不能设置为Integer.MAX_VALUE,否则两个Integer.MAX_VALUE相加会溢出导致出现负权public static int MaxValue = 100000;public static void main(String[] args) {Scanner input = new Scanner(System.in);System.out.println("请输入顶点数和边数:");//顶点数int vertex = input.nextInt();//边数int edge = input.nextInt();int[][] matrix = new int[vertex][vertex];//初始化邻接矩阵for (int i = 0; i < vertex; i++) {for (int j = 0; j < vertex; j++) {matrix[i][j] = MaxValue;}}for (int i = 0; i < edge; i++) {System.out.println("请输入第" + (i + 1) + "条边与其权值:");int source = input.nextInt();int target = input.nextInt();int weight = input.nextInt();matrix[source][target] = weight;}//单源最短路径,源点int source = input.nextInt();//调用dijstra算法计算最短路径dijstra(matrix, source);}public static void dijstra(int[][] matrix, int source) {//最短路径长度int[] shortest = new int[matrix.length];//判断该点的最短路径是否求出int[] visited = new int[matrix.length];//存储输出路径String[] path = new String[matrix.length];//初始化输出路径for (int i = 0; i < matrix.length; i++) {path[i] = new String(source + "->" + i);}//初始化源节点shortest[source] = 0;visited[source] = 1;for (int i = 1; i < matrix.length; i++) {int min = Integer.MAX_VALUE;int index = -1;for (int j = 0; j < matrix.length; j++) {//已经求出最短路径的节点不需要再加入计算并判断加入节点后是否存在更短路径if (visited[j] == 0 && matrix[source][j] < min) {min = matrix[source][j];index = j;}}//更新最短路径shortest[index] = min;visited[index] = 1;//更新从index跳到其它节点的较短路径for (int m = 0; m < matrix.length; m++) {if (visited[m] == 0 && matrix[source][index] + matrix[index][m] < matrix[source][m]) {matrix[source][m] = matrix[source][index] + matrix[index][m];path[m] = path[index] + "->" + m;}}}//打印最短路径for (int i = 0; i < matrix.length; i++) {if (i != source) {if (shortest[i] == MaxValue) {System.out.println(source + "到" + i + "不可达");} else {System.out.println(source + "到" + i + "的最短路径为:" + path[i] + ",最短距离是:" + shortest[i]);}}}}
}

3、Floyd算法

Floyd算法又称为插点法,是一种利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法,与Dijkstra算法类似。该算法是一种在具有正或负边缘权重(但没有负环)的加权图中找到最短路径的算法,即支持负权值但不支持负权环。弗洛伊德算法采用的是动态规划思想,其状态转移方程如下:

其中matrix[i,j]表示i到j的最短距离, k是穷举i到j之间可能经过的中间点,当中间点为k时,对整个矩阵即从ij的路径长度进行更新,对所有可能经过的中间点进行遍历以得到全局最优的最短路径。算法的单个执行将找到所有顶点对之间的最短路径长度,与迪杰斯特阿拉算法的计算目标有一些差异,迪杰斯特拉计算的是单源最短路径,而弗洛伊德计算的是多源最短路径,其时间复杂度为O(n³)。虽然它不返回路径本身的细节,但是可以通过对算法的简单修改来重建路径,我们利用这个思想,通过递归的方式访问每条路径经过的中间节点,对最终的路径进行输出。

算法思想:逐个顶点试探法

算法步骤:

  • 初始时设置一个n阶方阵,令其对角线元素为0,若存在弧<Vi,Vj>,则对应元素为权值;否则为无穷大——图的邻接矩阵;
  • 逐步试着在原直接路径中增加一个中间顶点,若加入中间点后路径变短,则修改之;否则,维持原值;
  • 所有顶点试探完毕,算法结束。

算法的代码实现:

public class FloydAlgorithm {public static int MaxValue = 100000;public static int[][] path;public static void main(String[] args) {Scanner input = new Scanner(System.in);System.out.println("请输入顶点数和边数:");//顶点数int vertex = input.nextInt();//边数int edge = input.nextInt();int[][] matrix = new int[vertex][vertex];//初始化邻接矩阵for (int i = 0; i < vertex; i++) {for (int j = 0; j < vertex; j++) {matrix[i][j] = MaxValue;}}//初始化路径数组path = new int[matrix.length][matrix.length];//初始化边权值for (int i = 0; i < edge; i++) {System.out.println("请输入第" + (i + 1) + "条边与其权值:");int source = input.nextInt();int target = input.nextInt();int weight = input.nextInt();matrix[source][target] = weight;}//调用算法计算最短路径floyd(matrix);}//非递归实现public static void floyd(int[][] matrix) {for (int i = 0; i < matrix.length; i++) {for (int j = 0; j < matrix.length; j++) {path[i][j] = -1;}}for (int m = 0; m < matrix.length; m++) {for (int i = 0; i < matrix.length; i++) {for (int j = 0; j < matrix.length; j++) {if (matrix[i][m] + matrix[m][j] < matrix[i][j]) {matrix[i][j] = matrix[i][m] + matrix[m][j];//记录经由哪个点到达path[i][j] = m;}}}}for (int i = 0; i < matrix.length; i++) {for (int j = 0; j < matrix.length; j++) {if (i != j) {if (matrix[i][j] == MaxValue) {System.out.println(i + "到" + j + "不可达");} else {System.out.print(i + "到" + j + "的最短路径长度是:" + matrix[i][j]);System.out.print("最短路径为:" + i + "->");findPath(i, j);System.out.println(j);}}}}}//递归寻找路径public static void findPath(int i, int j) {int m = path[i][j];if (m == -1) {return;}findPath(i, m);System.out.print(m + "->");findPath(m, j);}
}
  • 上述的三层循环中,第一层循环设置中间点k,第二层循环设置起始点i,第三层循环设置结束点j

  • 因为路径更新是根据新值和旧值比较获得的,最终的结果都是在最后一次迭代过程中对全局进行更新而得到的,中间的每次迭代只是一次局部调整而非最终结果。而不像迪杰斯特拉采用的贪心策略,每一次迭代都确定出一条最短路径,负权的出现使得不能保证每次迭代都是最优解。

4、Bellman-ford算法

BellmanFord算法功能:给定一个加权连通图,选取一个顶点,称为起点,求取起点到其它所有顶点之间的最短距离,其显著特点是可以求取含负权图的单源最短路径,可以来判断该图中是否有负权回路或者存在最短路径的点。

Bellman-Ford算法同样也是这样,它的每次循环也可以确定一个点的最短路,只不过代价很大,因为 Bellman-Ford每次循环都是操作所有边。既然代价这么大,相比Dijkstra算法,Bellman-Ford算法还有啥用?因为后者可以检测负权回路啊。Bellman-Ford算法的时间复杂度为 O(nm),其中n 为顶点数,m为边数。

BellmanFord算法思想:

  • 初始化所有点。每一个点保存一个值,表示从原点到达这个点的距离,将原点的值设为0,其它的点的值设为无穷大(表示不可达)。
  • 进行循环,循环下标为从1n-1n等于图中点的个数)。在循环内部,遍历所有的边,进行松弛计算
  • 遍历途中所有的边(edge(u,v)),判断是否存在这样情况:如果d(v) > d (u) + w(u,v),则返回false,表示途中存在从源点可达的权为负的回路;若不存在,则返回true,该数组记录的就是每一个点到源点的最小值。

算法代码实现:

/**  * 采用保存边的方式来存储图的的信息。  * p保存的是前驱节点,d保存的是源点到每一个点的最短距离。我们最后又做了一次判断,如果还有可以松弛  * 的边,那么可以保证的是图中有负权的环存在,这样的话就没有最短路径只说了,可以无限小。  */
public class BellmanFord {    private static final int m = 10000;    public static void main(String[] args) {    Adge a1 = new Adge(0, 1, -1);    Adge a2 = new Adge(0, 2, 4);    Adge a3 = new Adge(1, 2, 3);     Adge a4 = new Adge(3, 1, 1);    Adge a5 = new Adge(1, 3, 2);    Adge a6 = new Adge(3, 2, 5);    Adge a7 = new Adge(1, 4, 2);    Adge a8 = new Adge(4, 3, -3);    Adge[] as = new Adge[]{a1, a2, a3, a4, a5, a6, a7, a8};    int[] d = new int[5];    int[] p = new int[5];    d[0] = 0;    p[0] = 0;    for (int i = 1; i < d.length; i++) {    d[i] = m;    p[i] = -1;    }    bellman_Ford(as, d, p);    for (int i = 0; i < d.length; i++) {    System.out.println(d[i]);    }    }    private static void bellman_Ford(Adge[] as, int[] d, int[] p){    for(int i = 1; i < d.length; i++) {    for (int j = 0; j < as.length; j++) {    if (d[as[j].getV()] > d[as[j].getU()] + as[j].getW()) {    d[as[j].getV()] = d[as[j].getU()] + as[j].getW();    p[as[j].getV()] = as[j].getU();    }    }    }    for (int j = 0; j < as.length; j++) {    if (d[as[j].getV()] > d[as[j].getU()] + as[j].getW()) {    System.out.println("有负环");    }    }    }
}
class Adge {    private int u;    private int v;    private int w;    public Adge(int u, int v, int w) {    this.u = u;    this.v = v;    this.w = w;    }    public int getU() {    return u;    }    public void setU(int u) {    this.u = u;    }    public int getV() {    return v;    }    public void setV(int v) {    this.v = v;    }    public int getW() {    return w;    }    public void setW(int w) {    this.w = w;    }
}

5、SPFA算法

Bellman-Ford算法时间复杂度比较高,在于Bellman-Ford需要递推n次,每次递推需要扫描所有的边,在递推n次的过程中,很多判断是多余的,所以考虑用队列优化,减少不必要的判断,这种算法称为SPFAShortest Path Faster Algorithm

SPFA算法的流程:

使用一个优先队列来进行维护,初始时将源点加入队列,每次从队列中取出一个顶点,并对它所有相邻的节点进行松弛,如果某个顶点松弛成功,则将其入队,重复这样的过程,直至队列为空为止。时间复杂度在O(Km)(通常K2左右)一个顶点可以多次入队,但是如果有顶点入队次数大于n次,那就存在负环,此时应当返回存在负环信息。

SPFA算法寻找单源最短路径的时间复杂度为O(m*E)。(其中m为所有顶点进队的平均次数,可以证明m一般小于等于2*图顶点个数,E为给定图的边集合)

SPFA算法过程:

SPFA算法中同样可以用dist[]数组表示最短路长度,path数组保存路径,还需要设置cnt数组记录入队次数,vis数组记录当前是否在队列中

  • 取出队列头结点u,扫描从顶点u出发的每条边,设每条边的终点为v,边的权值为w(u, v)。如果dist[u] + w < dist[v],则将dist[v]修改成dist[u] + w<u, v>。修改path[v] = u,如果顶点v不在队列中,还需要将v加入队列并且入队次数加1。如果上述条件不成立就不做任何处理;

  • 重复上一步操作直至队列为空或者某个顶点入队次数大于n

SPFA算法代码实现:

import java.util.ArrayList;
import java.util.Scanner;public class Spfa {public long[] result;         //用于得到第s个顶点到其它顶点之间的最短距离//内部类,用于存放图的具体边数据class edge {public int a;  //边的起点public int b;  //边的终点public int value;   //边的权值edge(int a, int b, int value) {this.a = a;this.b = b;this.value = value;}}/** 参数n:给定图的顶点个数* 参数s:求取第s个顶点到其它所有顶点之间的最短距离* 参数edge:给定图的具体边* 函数功能:如果给定图不含负权回路,则可以得到最终结果,如果含有负权回路,则不能得到最终结果*/public boolean getShortestPaths(int n, int s, edge[] A) {ArrayList<Integer> list = new ArrayList<Integer>();result = new long[n];boolean[] used = new boolean[n];int[] num = new int[n];for(int i = 0;i < n;i++) {result[i] = Integer.MAX_VALUE;used[i] = false;}result[s] = 0;     //第s个顶点到自身距离为0used[s] = true;    //表示第s个顶点进入数组队num[s] = 1;       //表示第s个顶点已被遍历一次list.add(s);      //第s个顶点入队while(list.size() != 0) {int a = list.get(0);   //获取数组队中第一个元素list.remove(0);         //删除数组队中第一个元素for(int i = 0;i < A.length;i++) {//当list数组队的第一个元素等于边A[i]的起点时if(a == A[i].a && result[A[i].b] > result[A[i].a] + A[i].value) { result[A[i].b] = result[A[i].a] + A[i].value;if(!used[A[i].b]) {list.add(A[i].b);num[A[i].b]++;if(num[A[i].b] > n)return false;used[A[i].b] = true;   //表示边A[i]的终点b已进入数组队}}}used[a] = false;        //顶点a出数组对}return true;}public static void main(String[] args) {Spfa test = new Spfa();Scanner in = new Scanner(System.in);System.out.println("请输入一个图的顶点总数n起点下标s和边总数p:");int n = in.nextInt();int s = in.nextInt();int p = in.nextInt();        edge[] A = new edge[p];System.out.println("请输入具体边的数据:");for(int i = 0;i < p;i++) {int a = in.nextInt();int b = in.nextInt();int value = in.nextInt();A[i] = test.new edge(a, b, value);}if(test.getShortestPaths(n, s, A)) {for(int i = 0;i < test.result.length;i++)System.out.print(test.result[i]+" ");} elseSystem.out.println("给定图存在负环,没有最短距离");}
}

6、四种算法的比较

图论算法之最短路径(Dijkstra、Floyd、Bellman-ford和SPFA)相关推荐

  1. 【图论算法】最短路径算法(无权最短路径、Dijkstra算法、带负边值的图、无圈图)

    本篇博客将考察各种最短路径问题.     无权最短路径     Dijkstra 算法     具有负边值的图     无圈图     所有顶点对间的最短路径     最短路径的例子–词梯游戏 输入是 ...

  2. 遍历所有点的最短路径python_图遍历算法之最短路径Dijkstra算法

    一.最短路径问题(shortest path problem) 最短路径问题是图论研究中一个经典算法问题,旨在寻找图中两节点或单个节点到其他节点之间的最短路径.根据问题的不同,算法的具体形式包括: 确 ...

  3. 数学建模十大算法04—图论算法(最短路径、最小生成树、最大流问题、二分图)

    文章目录 一.最短路径问题 1.1 两个指定顶点之间的最短路径 1.1.1 Dijkstra算法 1.1.2 Matlab函数 1.2 每对顶点之间的最短路径 1.2.1 Dijkstra算法 1.2 ...

  4. 最短路径-dijkstra/floyd

    目录 floyd -dijkstra floyd floyd:用来求所有顶点之间的最短路径问题,求最短路径具体节点顺序,求各点之间最短路径长度 理解floyd: 二维矩阵图,就是不断通过测试新的节点k ...

  5. 图论-全源最短路径-对比Floyd算法与暴力Dijkstra算法

    题目 输入顶点数N,有向边数M,接下来M行输入格式为u,v,w分别代表两个顶点u,v和两点之间边的权值w.输出全源最短路径 输入样例: 6 8 0 1 1 0 3 4 0 4 4 1 3 2 2 5 ...

  6. 图解Bellman Ford算法

    Bellman Ford 单源最短路径算法[中字] Bellman Ford 单源最短路径算法[中字]_哔哩哔哩_bilibili 贝尔曼-福特算法(Bellman–Ford algorithm )油 ...

  7. Bellman Ford算法详解

    一.用途 1. Bellman Ford算法是解决拥有负权边最短路问题的方法之一.还有一种方法是SPFA算法. 2. 二者相比,SPFA算法在效率方面是优于Bellman Ford算法的.但在某些情况 ...

  8. 图论算法(二)-最短路径的Dijkstra [ 单源 ] 和Floyd[ 多源 ] 解法(JAVA )

    一.Dijkstra算法 问题描述:求一个点到任意个点的距离 思路:单源最短路径问题,使用Dijkstra算法 Input: 6 9 1 2 1 1 3 12 2 3 9 2 4 3 3 5 5 4 ...

  9. 图论-单源最短路径算法(拓扑,Dijkstra,Floyd,SPFA)

    前言 单源最短路径是学习图论算法的入门级台阶,但刚开始看的时候就蒙了,什么有环没环,有负权没负权,下面就来总结一下求单源最短路径的所有算法以及其适用的情况. 单源最短路径 设定图中一个点为源点,求其他 ...

最新文章

  1. Deep Learning(深度学习)学习笔记整理系列 一
  2. linux java连接redis_java 连接linux的redis 报错。但是linux 客户端可以连接redis
  3. 韩寒:一个产品经理的自我修养
  4. php ajax实现编辑资料,怎么用jQuery、Ajax、php实现这样的无刷新编辑功能?
  5. boost::log::attributes::make_function用法的测试程序
  6. OpenCV计算机视觉应用程序的交互式视觉调试
  7. 我的世界基岩版json_我的世界基岩版1.16
  8. Java读取指定目录下的所有文件名
  9. mac mysql本地连接数_Mac OS X下MySQL 5.0的默认连接数
  10. Java中 volatile 关键字的最全总结,赶快给自己查缺补漏吧!
  11. html 脚本 gdi,基于gdi的简单画图
  12. linux md5接口,md5-linux_shell(示例代码)
  13. JDBC调用存储过程,以及存储过程 事务的使用.....
  14. Shell脚本笔记(二)Shell变量
  15. 树莓派编译ch934x usb转多串口驱动
  16. 跨境电商如何利用Quora帮你引上万流量
  17. Self-paced Learning 自步学习
  18. linux phpcms,PHPCMS任意文件下载之exp编写
  19. 【web前端】第二天-HTML标签(下)
  20. 【翻译】HCP: A Flexible CNN Framework for Multi-Label Image Classification

热门文章

  1. 江苏农牧科技职业学院计算机应用技术,★江苏农牧科技职业学院_招生之家
  2. 【社区团购】打破传统消费模式,小程序源码+页面DIY+限时抢购+优惠券
  3. 电子信息工程技术与计算机网络技术,计算机网络技术对电子信息工程的影响(word版)...
  4. RAII + 接口模式
  5. 【厉害了FPGA】Verilog实现接收帧数据的一种方法(帧数据同步搜索检测)
  6. 深度学习的文本多分类思维导图
  7. 转载:Handlebar使用教程
  8. linux怎么看一个端口是否可用,LINUX中查看某个端口是否被占用的方法
  9. oracle 查询数据库io,查看Oracle数据文件和磁盘i/o情况
  10. Java学习-IO流-打印流