最短路算法详解(Dijkstra 算法,Bellman-Ford 算法,Floyd-Warshall 算法)

news2024/11/13 14:36:17

文章目录

  • 一、Dijkstra 算法
  • 二、Bellman-Ford 算法
  • 三、Floyd-Warshall 算法

由于文章篇幅有限,下面都只给出算法对应的部分代码,需要全部代码调试参考的请点击: 图的源码

最短路径问题:从在带权图的某一顶点出发,找出一条通往另一顶点的最短路径,最短也就是沿路径各边的权值总和达到最小。涉及到三个算法:

  1. 单源最短路径:Dijkstra 算法(迪杰斯特拉算法)(不能解决负权图)
  2. 单源最短路径:Bellman-Ford 算法(贝尔曼-福特算法)(可以解决负权图)
  3. 多源最短路径:Floyd-Warshall 算法(弗洛伊德算法)(可以解决负权图)

注意:本文采用的图是邻接矩阵实现的。

一、Dijkstra 算法

  • 算法概括:

Dijkstra 是一种求解非负权图上单源最短路径的算法。

  • 算法流程:

其过程为:将结点分成两个集合,已确定最短路长度的点集(记为 S 集合)和未确定最短路长度的点集(记为 T 集合)。一开始所有的点都属于 T 集合。

需要对 dist 数组(存储最短路的长度)进行初始化,除了起点设置为 0 外,其它的都设置为无穷大。

接着重复如下操作:

  • 从 T 集合中,选取一个最短路长度最小的节点,移动到 S 集合中。
  • 对那些刚刚加入 S 集合的结点的所有出边执行松弛操作

直到 T 集合为空,算法结束。

  • 时间复杂度:

每次确定一个顶点 O(n),并松弛其连接出去的所有边(最多有 n - 1条),其时间复杂度为 O(n ^ 2)。

松弛操作:在这里插入图片描述
对于起点 B 到达点 A,松弛操作对应如下的式子:dis(A) = min(dis(A) , dis(C ) + 边 CA),当确定一个顶点的最短路径后,对其连出去的所有边进行松弛操作

过程演示如下图:
在这里插入图片描述

  • 代码如下:
/**
     * @param vSrc  起点
     * @param dist  存储从起点到各个顶点的最小权值
     * @param pPath 存储各个顶点到起点的最短权值路径
     */
    public void dijkstra(char vSrc, int[] dist, int[] pPath) {
        //用来标记已经确定的点
        boolean[] vis = new boolean[size];
        //获取起点对应下标
        int srcIndex = getVIndex(vSrc);
        //初始化 dist 和 pPath
        Arrays.fill(dist, Integer.MAX_VALUE);
        Arrays.fill(pPath, -1);//最终结果为 -1 说明起点到达不了
        //起点到起点为 0
        dist[srcIndex] = 0;
        pPath[srcIndex] = srcIndex;
        //填写 dist
        for (int i = 0; i < size; i++) {//一共要填写 size 次
            //确定一条最短的路径
            int min = Integer.MAX_VALUE;
            int u = srcIndex;
            for (int v = 0; v < size; v++) {
                if (vis[v] == false && min > dist[v]) {
                    min = dist[v];
                    u = v;
                }
            }
            vis[u] = true;
            //进行松弛操作 + 填写 pPath
            //一个顶点出度最大为 size,把不存在的排除即可
            for (int v = 0; v < size; v++) {
                if (vis[v] == false && matrix[u][v] != Integer.MAX_VALUE && dist[u] + matrix[u][v] < dist[v]) {
                    dist[v] = dist[u] + matrix[u][v];
                    pPath[v] = u;
                }
            }
        }
    }
  • 测试 Dijkstra 算法:

通过打印最短路的顶点组成和最短路的长度,即可验证正确性。

public void printShortPath(char vSrc,int[] dist,int[] pPath) {
        int srcIndex = getIndexOfV(vSrc);

        int n = arrayV.length;

        for (int i = 0; i < n; i++) {
            //i下标正好是起点  则不进行路径的打印
            if(i != srcIndex) {
                ArrayList<Integer> path = new ArrayList<>();
                int pathI = i;
                while (pathI != srcIndex) {
                    path.add(pathI);
                    pathI = pPath[pathI];
                }
                path.add(srcIndex);

                Collections.reverse(path);

                for (int pos : path) {
                    System.out.print(arrayV[pos]+" -> ");
                }
                System.out.println(dist[i]);
            }
        }
    }

    public static void testGraphDijkstra() {
        String str = "syztx";
        char[] array = str.toCharArray();
        GraphByMatrix g = new GraphByMatrix(str.length(),true);
        g.initArrayV(array);
        g.addEdge('s', 't', 10);
        g.addEdge('s', 'y', 5);
        g.addEdge('y', 't', 3);
        g.addEdge('y', 'x', 9);
        g.addEdge('y', 'z', 2);
        g.addEdge('z', 's', 7);
        g.addEdge('z', 'x', 6);
        g.addEdge('t', 'y', 2);
        g.addEdge('t', 'x', 1);
        g.addEdge('x', 'z', 4);
        int[] dist = new int[array.length];
        int[] parentPath = new int[array.length];
        g.dijkstra('s', dist, parentPath);
        g.printShortPath('s', dist, parentPath);
    }

运行结果为:

构建的图为过程演示时的图。

在这里插入图片描述
显然正确🎉🎉🎉

二、Bellman-Ford 算法

  • 算法概括:

Bellman–Ford 算法是一种基于松弛操作的最短路算法,可以求出有负权的图的最短路,并可以对最短路不存在的情况进行判断。

  • 算法流程:

Bellman–Ford 算法所做的,就是不断尝试对图上每一条边进行松弛。我们每进行一轮循环,就对图上所有的边都尝试进行一次松弛操作,当一次循环中没有成功松弛操作时,算法停止。

  • 时间复杂度:

在最短路存在的情况下,由于一次松弛操作会使最短路的边数至少 + 1,而最短路的边数最多为 n - 1,因此整个算法最多执行 n - 1 轮松弛操作。故总时间复杂度为 O(n * m)。其中 n 为顶点个数,m 为 边的个数。

  • 代码如下:
/**
     *
     * @param vSrc 起点
     * @param dist 存储从起点到各个顶点的最小权值
     * @param pPath 存储各个顶点到起点的最短权值路径
     * @return 返回 true 表示该图不存在负权回路,返回 false 表示该图存在负权回路
     */
    public boolean bellmanFord(char vSrc, int[] dist, int[] pPath) {
        //获取顶点下标
        int srcIndex = getVIndex(vSrc);
        //初始化数据
        Arrays.fill(dist, Integer.MAX_VALUE);
        Arrays.fill(pPath, -1);
        dist[srcIndex] = 0;
        pPath[srcIndex] = srcIndex;
        //松弛操作
        //进行 size 次
        for (int k = 0; k < size; k++) {
            //遍历每条边
            for (int i = 0; i < size; i++) {
                for (int j = 0; j < size; j++) {
                    if (matrix[i][j] != Integer.MAX_VALUE && dist[i] + matrix[i][j] < dist[j]) {
                        dist[j] = dist[i] + matrix[i][j];
                        pPath[j] = i;
                    }
                }
            }
        }
        //判断是否存在负回路
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                if (matrix[i][j] != Integer.MAX_VALUE && dist[i] + matrix[i][j] < dist[j]) {
                    return false;//存在负回路
                }
            }
        }
        return true;//不存在负回路
    }
  • 测试 Bellman-Ford 算法:

下面的测试案例就是根据这张图进行设计的。
在这里插入图片描述

public static void testGraphBellmanFord() {
        String str = "syztx";
        char[] array = str.toCharArray();
        GraphByMatrix g = new GraphByMatrix(str.length(), true);
        g.initArrayV(array);
        for (int i = 0; i < str.length(); i++) {
            Arrays.fill(g.matrix[i], Integer.MAX_VALUE);
        }
        //负权回路实例
//        g.addEdge('s', 't', 6);
//        g.addEdge('s', 'y', 7);
//        g.addEdge('y', 'z', 9);
//        g.addEdge('y', 'x', -3);
//        g.addEdge('y', 's', 1);
//        g.addEdge('z', 's', 2);
//        g.addEdge('z', 'x', 7);
//        g.addEdge('t', 'x', 5);
//        g.addEdge('t', 'y', -8);
//        g.addEdge('t', 'z', -4);
//        g.addEdge('x', 't', -2);

        //不存在负权回路的情况
        g.addEdge('s', 't', 6);
        g.addEdge('s', 'y', 7);
        g.addEdge('y', 'z', 9);
        g.addEdge('y', 'x', -3);
        g.addEdge('z', 's', 2);
        g.addEdge('z', 'x', 7);
        g.addEdge('t', 'x', 5);
        g.addEdge('t', 'y', 8);
        g.addEdge('t', 'z', -4);
        g.addEdge('x', 't', -2);

        int[] dist = new int[array.length];
        int[] parentPath = new int[array.length];
        boolean fig = g.bellmanFord('s', dist, parentPath);
        if (fig) {
            g.printShortPath('s', dist, parentPath);
        } else {
            System.out.println("存在负权回路");
        }

    }

运行结果如下:
在这里插入图片描述

三、Floyd-Warshall 算法

  • 算法概括:

Floyd-Warshall 算法是用来求任意两个结点之间的最短路的。适用于任何图,不管有向无向,边权正负,但是最短路必须存在(不能有个负环) ,复杂度比较高。

  • 算法流程:

Floyd-Warshall 算法的原理是动态规划

设 D(i,j,k) 为从 i 到 j 的只以(1…k)集合中的节点为中间节点的最短路径的长度。

  1. 若最短路径不经过 k,则 D(i,j,k) = D(i,j,k - 1)。
  2. 若最短路径经过 k,则 D(i,j,k) = D(i,k,k - 1) + D(k,j,k - 1)。(k - 1 不影响起点和终点取 k)

因此,D(i,j,k) = min(D(i,j,k - 1),D(i,k,k - 1) + D(k,j,k - 1))。

在实际算法中,为了节省空间,可以直接在原来空间上进行迭代,这样空间可降至二维。

  • 时间复杂度:

k 的取值从 1 到 n,每次 k 都要进行一次完整的动态规划。时间复杂度为 O(n ^ 3)。

  • 代码如下:
/**
     *
     * @param dist 存储各个顶点之间的最小权值
     * @param pPath 存储各个顶点之间的最短权值路径
     */
    public void floyWarShall(int[][] dist, int[][] pPath) {
        //初始化
        for (int i = 0; i < size; i++) {
            Arrays.fill(dist[i], Integer.MAX_VALUE);
            Arrays.fill(pPath[i], -1);
        }
        //将边填入到 dist 数据,给后续动态规划初始化
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                //存在边
                if (matrix[i][j] != Integer.MAX_VALUE) {
                    dist[i][j] = matrix[i][j];
                    pPath[i][j] = i;
                    //边不存在的情况
                } else {
                    pPath[i][j] = -1;
                }
                if (i == j) {
                    dist[i][j] = 0;
                    pPath[i][j] = -1;
                }
            }
        }
        //动态规划
        for (int k = 0; k < size; k++) {
            for (int i = 0; i < size; i++) {
                for (int j = 0; j < size; j++) {
                    if (dist[i][k] != Integer.MAX_VALUE && dist[k][j] != Integer.MAX_VALUE &&
                            dist[i][k] + dist[k][j] < dist[i][j]) {
                        dist[i][j] = dist[i][k] + dist[k][j];
                        pPath[i][j] = pPath[k][j];
                    }
                }
            }
        }
        
        //下面的代码为测试代码,打印 dist,和 pPath

        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                if (dist[i][j] == Integer.MAX_VALUE) {
                    System.out.print(" * ");
                } else {
                    System.out.print(dist[i][j] + " ");
                }
            }
            System.out.println();
        }
        System.out.println("=========打印路径==========");
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                System.out.print(pPath[i][j] + " ");
            }
            System.out.println();
        }
        System.out.println("=================");
    }
  • 测试 Floyd-Warshall 算法:

测试图例如下:
在这里插入图片描述

public static void testGraphFloydWarShall() {
        String str = "12345";
        char[] array = str.toCharArray();
        GraphByMatrix g = new GraphByMatrix(str.length(), true);
        g.initArrayV(array);
        //没有边,设置为 最大值。
        for (int i = 0; i < g.size; i++) {
            Arrays.fill(g.matrix[i], Integer.MAX_VALUE);
        }
        g.addEdge('1', '2', 3);
        g.addEdge('1', '3', 8);
        g.addEdge('1', '5', -4);
        g.addEdge('2', '4', 1);
        g.addEdge('2', '5', 7);
        g.addEdge('3', '2', 4);
        g.addEdge('4', '1', 2);
        g.addEdge('4', '3', -5);
        g.addEdge('5', '4', 6);
        int[][] dist = new int[array.length][array.length];
        int[][] parentPath = new int[array.length][array.length];
        g.floyWarShall(dist, parentPath);
        for (int i = 0; i < array.length; i++) {
            g.printShortPath(array[i],dist[i],parentPath[i]);
        }
    }

运行结果如下:
在这里插入图片描述

下图为测试图例的过程图。
在这里插入图片描述
通过对比可以发现,我们的运行结果是正确的(路径会差 1,因为下标是从 0 开始的)。

参考资料:OI-wiki。

结语:
其实写博客不仅仅是为了教大家,同时这也有利于我巩固知识点,和做一个学习的总结,由于作者水平有限,对文章有任何问题还请指出,非常感谢。如果大家有所收获的话还请不要吝啬你们的点赞收藏和关注,这可以激励我写出更加优秀的文章。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2091397.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【PyCharm激活码】2024年最新pycharm专业版激活码+安装教程!

一、PyCharm激活 激活码&#xff1a; KQ8KMJ77TY-eyJsaWNlbnNlSWQiOiJLUThLTUo3N1RZIiwibGljZW5zZWVOYW1lIjoiVW5pdmVyc2l0YXMgTmVnZXJpIE1hbGFuZyIsImxpY2Vuc2VlVHlwZSI6IkNMQVNTUk9PTSIsImFzc2lnbmVlTmFtZSI6IkpldOWFqOWutuahtiDorqTlh4blupflkI0iLCJhc3NpZ25lZUVtYWlsIjoi…

ArcEngine二次开发实用函数18:使用shp矢量对栅格文件进行掩模和GP授权获取

目录 1. 权限设置 2. 添加如下引用 3. 核心代码: 首先要确定要使用的gp工具需要什么权限,这个可以在工具的帮助中查看;获取权限之后,引用名称空间,编写处理代码: 下面给出具体的实例代码: 1. 权限设置 ESRI.ArcGIS.RuntimeManager.Bind(ESRI.ArcGIS.ProductCode.Eng…

介绍一下最近很火的一款游戏黑神话悟空,以及国产游戏面临的挑战

《黑神话&#xff1a;悟空》是一款由杭州游科互动科技有限公司开发的单机动作角色扮演游戏&#xff0c;以中国古典名著《西游记》为背景。游戏在2024年8月20日上线&#xff0c;支持PC&#xff08;Steam、Epic、Wegame&#xff09;和PlayStation 5平台&#xff0c;未来还将登陆X…

OpenCV绘图函数(13)绘制多边形函数函数polylines()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 画几条多边形曲线 函数原型 void cv::polylines (InputOutputArray img,InputArrayOfArrays pts,bool isClosed,const Scalar & color…

浅谈 Android 15 新 API:确保 TextView 完整展示、不被切断~

本文为稀土掘金技术社区首发签约文章&#xff0c;30天内禁止转载&#xff0c;30天后未获授权禁止转载&#xff0c;侵权必究&#xff01; 前言 很多语言和文字拥有特殊的、复杂的写法、画法&#xff0c;一个字符可能延伸到前一个字符的区域&#xff0c;甚至后一个字符的区域。 …

力扣375.猜数字大小 II

力扣375.猜数字大小 II dp dp[i][j]是说依次以从i到j的数字作为分割点(猜的数)&#xff0c;必定赢的游戏所用钱的最小值。 枚举每一列&#xff0c;从下往上算出dp[i][j]&#xff0c;最终答案为dp[1][n] class Solution {public:int getMoneyAmount(int n) {if(n 1)retu…

巧用scss实现一个通用的媒介查询代码

巧用scss实现一个通用的媒介查询代码 效果展示 实现代码 <template><div class"page-root"><div class"header"></div><div class"content"><div class"car-item" v-for"item in 9">…

20行为型设计模式——访问者模式

一、访问者模介绍 访问者模式&#xff08;Visitor Pattern&#xff09;是一种行为型设计模式&#xff0c;用于将操作封装在访问者对象中&#xff0c;以便在不改变被访问对象的类的前提下&#xff0c;定义新的操作。它允许你在不修改现有代码的情况下&#xff0c;向对象结构中添…

类和对象以及内存管理

对象拷贝时的编译器优化 现代编译器会为了尽可能提高程序的效率&#xff0c;在不影响正确性的情况下会尽可能减少⼀些传参和传返回值的过程中可以省略的拷贝。如何优化C标准并没有严格规定&#xff0c;各个编译器会根据情况自行处理。当前主流的相对新⼀点的编译器对于连续⼀个…

电池信息 v5.29.11 高级版,智能优化充电,最多可延长50%电池寿命

Charging Master 是一款非常实用的安卓 APP&#xff0c;专注于为您的手机充电提供最佳体验。借助其智能优化功能&#xff0c;Charging Master 能够最大程度地延长电池寿命&#xff0c;最多可达 50% 的节省。此外&#xff0c;该应用还提供了一系列功能&#xff0c;助您更好地管理…

提升团队效率的9款免费办公工具评测

本文主要介绍了以下9款协同办公软件&#xff1a;1.Worktile&#xff1b;2.PingCode&#xff1b;3.石墨文档&#xff1b;4.Teambition&#xff1b;5.蓝湖&#xff1b;6.工作宝&#xff1b;7.飞书&#xff1b;8.Asana&#xff1b;9.ClickUp。 在现代职场中&#xff0c;团队协作已…

GD - GD32350R_EVAL - PWM实验和验证1

文章目录 GD - GD32350R_EVAL - PWM实验和验证1概述笔记实验设计实验环境GD32350R_EVAL 的硬件连接修改程序配置 - 只产生PWM波&#xff0c;不要CMP清除波形TIMER0时钟设置TIMER0的PWM设置参数设置main()中PWM波形的开启代码示波器测量结果如果要产生4KHZ的PWM需要设置怎样的参…

在centos系统中kill掉指定进程

如上图&#xff0c;我想kill掉 python3 func_tg_1_vps.py这个进程&#xff08;而不kill掉python3 func_tg_2_vps.py&#xff09;。 解决方法&#xff1a; 第一步&#xff1a;首先使用ps -ef | grep python3命令&#xff0c;查出所有包含python3的命令 拿其中一条讲解 root …

开放式耳机漏音有多大?开放式耳机是否值得购买?

开放式耳机确实存在漏音的问题&#xff0c;这是因为其设计原理决定的。开放式耳机不像封闭式耳机那样完全封闭耳道&#xff0c;因此声音会向外散播&#xff0c;导致漏音。不过&#xff0c;随着技术的发展&#xff0c;许多耳机制造商已经开始着手解决这个问题&#xff0c;通过改…

Git之2.0版本重要特性及用法实例(五十六)

简介&#xff1a; CSDN博客专家、《Android系统多媒体进阶实战》一书作者. 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a; 多媒体系统工程师系列…

VScode 使用记录

插件 1、代码提示插件&#xff1a;Codeium 安装说明&#xff1a;Codeium&#xff1a;强大且免费的AI智能编程助手 - Su的技术博客 (verysu.com) 用google账号登陆&#xff0c;跳转按照官网给的三个步骤来 step1&#xff1a;复制token&#xff1b; step2&#xff1a;在文件页…

中秋佳节,南卡Runner Pro5骨传导耳机让团圆更圆满!

中秋节&#xff0c;这个承载着温馨与团圆的节日&#xff0c;是向亲朋好友表达深情厚意的绝佳时刻。在这样一个特别的日子里&#xff0c;挑选一份既实用又充满科技感的礼物&#xff0c;无疑能够给人们带来惊喜与感动。南卡Runner Pro5骨传导耳机&#xff0c;凭借其创新的设计和卓…

绿色消费新动力:‘众店‘模式引领数字经济下的零售创新

在数字浪潮的推动下&#xff0c;传统零售业正经历着前所未有的转型。绿色消费积分系统&#xff0c;在这一变革中崭露头角&#xff0c;成为新兴消费平台的佼佼者。 一、"众店"平台的快速崛起 仅用两年时间&#xff0c;"众店"平台就实现了巨大的飞跃&#x…

代码随想录算法训练营day58:图论08:拓扑排序精讲;dijkstra(朴素版)精讲

拓扑排序精讲 卡码网&#xff1a;117. 软件构建(opens new window) 题目描述&#xff1a; 某个大型软件项目的构建系统拥有 N 个文件&#xff0c;文件编号从 0 到 N - 1&#xff0c;在这些文件中&#xff0c;某些文件依赖于其他文件的内容&#xff0c;这意味着如果文件 A 依…

4_PMSM基于s函数的仿真建模_1

为了检验电机仿真模型的正确性&#xff0c;&#xff0c;以基于s函数方法搭建的数学模型为例&#xff0c;搭建如图的三相所示的简单三相PMSM矢量控制系统&#xff0c;此模型忽略了PWM逆变器的影响。另外&#xff0c;感兴趣的同志可以对基于Simulink方法搭建的仿真模型进行验证。…