【算法】最短路径——弗洛伊德 (Floyd) 算法

目录

  • 1.概述
  • 2.代码实现
  • 3.扩展
  • 3.应用

1.概述

(1)弗洛伊德 (Floyd) 算法又称为插点法,是一种利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法,与 Dijkstra 算法类似。该算法名称以创始人之一、1978 年图灵奖获得者、斯坦福大学计算机科学系教授罗伯特·弗洛伊德命名。

(2)弗洛伊德 (Floyd) 算法的具体思路如下:

  • 创建一个二维数组 dist,用于存储任意两个顶点之间的最短路径长度。
  • 初始化 dist 数组,将每条边的权重赋值给对应的 dist[i][j],其中 i 和 j 分别表示边的起点和终点。
  • 使用三重循环,分别遍历每一个顶点 k,将顶点 k 作为中间节点,更新 dist 数组。
  • 对于每一对顶点 i 和 j,如果 dist[i][j] 大于 dist[i][k] + dist[k][j],则更新 dist[i][j] 为 dist[i][k] + dist[k][j]。
  • 最终,dist 数组中存储的就是每对顶点之间的最短路径长度。

该算法的核心思想是通过动态规划逐步构建最短路径。通过逐渐扩展考虑的中间节点集合,可以确保得到最终的最短路径。通过多次迭代,每对顶点之间的最短路径逐渐更新,直到遍历完所有的中间节点,得到最后的最短路径矩阵。

(3)弗洛伊德 (Floyd) 算法的时间复杂度和空间复杂度分别如下:

  • 时间复杂度为 O(V3),弗洛伊德算法使用三重循环来迭代更新所有顶点对之间的最短路径,其中 V 表示图中顶点的数量。因此,算法的时间复杂度为 O(V3)。
  • 空间复杂度为 O(V2):算法需要使用一个二维数组来存储图中顶点对之间的最短路径距离,因此空间复杂度为 O(V2)。

有关 Dijkstra 算法的具体介绍可以参考【算法】最短路径——迪杰斯特拉 (Dijkstra) 算法这篇文章。

2.代码实现

(1)有关 Floyd 算法分别使用邻接矩阵和邻接表实现的说明如下:

  • Floyd 算法可以使用邻接表实现,但相比邻接矩阵,使用邻接表会增加一些复杂度,因为邻接表中存储了每个节点的出边信息,并没有存储到达该节点的边的信息。因此,在计算两个节点之间的距离时,需要遍历邻接表中连接这两个节点的所有边,这可能会使得算法的时间复杂度和空间复杂度都增加。另外,使用邻接表需要额外的开销来维护节点之间的关系。
  • 具体实现时,可以将每个节点的出边信息存储在一个链表、向量等动态数据结构中。为了能够以常数时间检查两个节点之间是否有边,可以使用哈希表来存储每个节点的出边信息。在遍历所有节点对时,对于每对节点 i 和 j,需要分别遍历它们的出边列表,以找到连接它们的路径。
  • 总之,虽然 Floyd 算法可以使用邻接表实现,但邻接矩阵更为简单和直观,并且在实现中运行时间更短和更节省空间。只有在图的规模比较大、稀疏程度比较高时,才考虑使用邻接表来表示图和实现弗洛伊德算法。

(2)下面给出 Floyd 算法的邻接矩阵实现:

class Solution {
    /*
         graph: 用于表示图的邻接矩阵
         返回值: 最短路径矩阵
    */
    public int[][] floyd(int[][] graph) {
        int n = graph.length;
        //初始化距离矩阵为邻接矩阵
        int[][] dist = new int[n][n];
        for (int i = 0; i < n; i++) {
            System.arraycopy(graph[i], 0, dist[i], 0, n);
        }
        //三重循环,依次计算每对节点之间的最短路径
        for (int k = 0; k < n; k++) {
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    if (dist[i][k] != Integer.MAX_VALUE && dist[k][j] != Integer.MAX_VALUE) {
                        //如果经过中间节点 k 更短,则更新路径长度
                        dist[i][j] = Math.min(dist[i][j], dist[i][k] + dist[k][j]);
                    }
                }
            }
        }
        return dist;
    }
}

(3)本测试中的加权无向图如下所示。
在这里插入图片描述

class Test {
	public static void main(String[] args) {
        //图的顶点数
        int n = 7;
        int[][] graph = new int[n][n];
        //初始化邻接矩阵,初始化为 Integer.MAX_VALUE 表示不可达
        for (int i = 0; i < n; i++) {
            Arrays.fill(graph[i], Integer.MAX_VALUE);
            graph[i][i] = 0;
        }
        //添加图的边
        graph[0][1] = 9;
        graph[0][5] = 1;
        graph[1][0] = 9;
        graph[1][2] = 4;
        graph[1][6] = 3;
        graph[2][1] = 4;
        graph[2][3] = 2;
        graph[3][2] = 2;
        graph[3][4] = 6;
        graph[3][6] = 5;
        graph[4][3] = 6;
        graph[4][5] = 8;
        graph[4][6] = 7;
        graph[5][0] = 1;
        graph[5][4] = 8;
        graph[6][1] = 3;
        graph[6][3] = 5;
        graph[6][4] = 7;

        Solution solution = new Solution();
        int[][] dist = solution.floyd(graph);
        //输出最短路径矩阵
        System.out.println("The shortest path matrix:");
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (dist[i][j] == Integer.MAX_VALUE) {
                    System.out.print("INF \t");
                } else {
                    System.out.print(dist[i][j] + " \t");
                }
            }
            System.out.println();
        }
    }
}

输出结果如下:

The shortest path matrix:
0 	9 	13 	15 	9 	1 	12 	
9 	0 	4 	6 	10 	10 	3 	
13 	4 	0 	2 	8 	14 	7 	
15 	6 	2 	0 	6 	14 	5 	
9 	10 	8 	6 	0 	8 	7 	
1 	10 	14 	14 	8 	0 	13 	
12 	3 	7 	5 	7 	13 	0 

3.扩展

(1)如果要求出每对顶点最短路径之间依次经过的节点,我们可以再增加一个矩阵来保存路径信息。设 paths[i][j] 表示节点i到节点j的最短路径经过的顶点序列。则 paths[i][j] 的计算如下:

  • 如果 dist[i][j] 为 INF(即节点 i 无法到达节点 j),则 paths[i][j] 为 null
  • 如果 i 和 j 之间有一条边(即 graph[i][j] 不为 INF),则 paths[i][j] 的值为 [i, j]
  • 否则,如果 dist[i][j] 可由 i 到达一个中间节点 k,然后由 k 到达 j 的路径更短,则在 paths[i][j] 中增加 k,并将 i->j 的最短路径更新为 i->k->j

(2)具体实现时,可以在弗洛伊德算法中增加一个与 dist 矩阵同样大小的 paths 矩阵,通过修改弗洛伊德算法中更新距离的语句,同时更新 paths 矩阵中的值,最终输出 paths 矩阵中的每个元素即可。具体实现如下所示:

class Solution {
    /*
         graph: 用于表示图的邻接矩阵
         返回值: 路径矩阵
    */
    public int[][] floydWithPath(int[][] graph) {
        int n = graph.length;
        //初始化距离矩阵和路径矩阵
        int[][] dist = new int[n][n];
        int[][] paths = new int[n][n];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                dist[i][j] = graph[i][j];
                if (graph[i][j] != Integer.MAX_VALUE) {
                    paths[i][j] = j;
                } else {
                    paths[i][j] = -1;
                }
            }
        }

        //三重循环,依次计算每对节点之间的最短路径
        for (int k = 0; k < n; k++) {
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    if (dist[i][k] != Integer.MAX_VALUE && dist[k][j] != Integer.MAX_VALUE) {
                        // 如果经过中间节点 k 更短,则更新路径长度和路径矩阵
                        if (dist[i][j] > dist[i][k] + dist[k][j]) {
                            dist[i][j] = dist[i][k] + dist[k][j];
                            paths[i][j] = paths[i][k];
                        }
                    }
                }
            }
        }
        return paths;
    }
}

(3)本测试中的加权无向图与上面一样,测试代码如下所示:

class Test {
	public static void main(String[] args) {
        //图的顶点数
        int n = 7;
        int[][] graph = new int[n][n];
        //初始化邻接矩阵,初始化为 Integer.MAX_VALUE 表示不可达
        for (int i = 0; i < n; i++) {
            Arrays.fill(graph[i], Integer.MAX_VALUE);
            graph[i][i] = 0;
        }
        //添加图的边
        graph[0][1] = 9;
        graph[0][5] = 1;
        graph[1][0] = 9;
        graph[1][2] = 4;
        graph[1][6] = 3;
        graph[2][1] = 4;
        graph[2][3] = 2;
        graph[3][2] = 2;
        graph[3][4] = 6;
        graph[3][6] = 5;
        graph[4][3] = 6;
        graph[4][5] = 8;
        graph[4][6] = 7;
        graph[5][0] = 1;
        graph[5][4] = 8;
        graph[6][1] = 3;
        graph[6][3] = 5;
        graph[6][4] = 7;

        Solution solution = new Solution();
        int[][] paths = solution.floydWithPath(graph);
        System.out.println("The path matrix:");
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (paths[i][j] == -1) {
                    System.out.print("null  ");
                } else {
                    List<Integer> p = new ArrayList<>();
                    int cur = i;
                    while (cur != j) {
                        p.add(cur);
                        cur = paths[cur][j];
                    }
                    p.add(j);
                    System.out.print(p + "  ");
                }
            }
            System.out.println();
        }
    }
}

输出结果如下:

The path matrix:
[0]  [0, 1]  [0, 1, 2]  [0, 1, 2, 3]  [0, 5, 4]  [0, 5]  [0, 1, 6]  
[1, 0]  [1]  [1, 2]  [1, 2, 3]  [1, 6, 4]  [1, 0, 5]  [1, 6]  
[2, 1, 0]  [2, 1]  [2]  [2, 3]  [2, 3, 4]  [2, 1, 0, 5]  [2, 1, 6]  
[3, 2, 1, 0]  [3, 2, 1]  [3, 2]  [3]  [3, 4]  [3, 4, 5]  [3, 6]  
[4, 5, 0]  [4, 6, 1]  [4, 3, 2]  [4, 3]  [4]  [4, 5]  [4, 6]  
[5, 0]  [5, 0, 1]  [5, 0, 1, 2]  [5, 4, 3]  [5, 4]  [5]  [5, 0, 1, 6]  
[6, 1, 0]  [6, 1]  [6, 1, 2]  [6, 3]  [6, 4]  [6, 1, 0, 5]  [6] 

3.应用

(1)弗洛伊德 (Floyd) 算法的应用场景包括但不限于以下几个方面:

  • 多源最短路径问题:弗洛伊德算法可以解决图中任意两个顶点之间的最短路径问题。这在路由算法、网络通信中非常有用,可以帮助确定从一个网络节点到其他节点的最短路径。
  • 公交网络规划:在城市公交系统中,弗洛伊德算法可以用于计算任意两个站点之间的最短路径,从而帮助规划公交线路和乘车路线。
  • 遗传算法中的距离计算:在遗传算法中,常常需要计算个体之间的距离或相似度,弗洛伊德算法可以用于计算从一个个体到其他个体之间的最短距离,以便进行后续的选择、交叉和变异操作。
  • 最优路径规划:弗洛伊德算法可以用于在地图导航系统中计算最短路径,帮助用户在城市道路网中找到最佳的行驶路径。
  • 交通流量优化:弗洛伊德算法可以用于计算交通网络中的最短路径,以优化交通流量分配,避免拥堵和优化道路资源利用。

(2)大家可以去 LeetCode 上找相关的 Floyd 算法的题目来练习,或者也可以直接查看 LeetCode 算法刷题目录 (Java) 这篇文章中的最短路径章节。如果大家发现文章中的错误之处,可在评论区中指出。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

应用在智能照明系统中的LED防蓝光灯珠

智能照明系统是利用电磁调压及电子感应技术&#xff0c;对供电进行实时监控与跟踪&#xff0c;自动平滑地调节电路的电压和电流幅度&#xff0c;改善照明电路中不平衡负荷所带来的额外功耗&#xff0c;提高功率因数&#xff0c;降低灯具和线路的工作温度&#xff0c;达到优化供…

到手价的低价监测怎么做到

品牌在做数据监测时&#xff0c;要考虑是否是到手价的监测&#xff0c;如果只是监测页面价的低价&#xff0c;真实情况是会漏掉很多的低价数据&#xff0c;如果是做经销商的低价监测工作&#xff0c;这样的监测方式下的数据会降低品牌对渠道的掌控力&#xff0c;因为监测的不是…

PPP协议_基础知识

ppp协议 点对点协议PPP(Point-to-Point Protocol)是目前使用最广泛的点对点数据链路层协议。 一.ppp协议的组成 PPP协议为在点对点链路传输各种协议数据报提供了一个标准方法&#xff0c;主要由以下三部分构成: 对各种协议数据报的封装方法(封装成帧)链路控制协议LCP    …

揭秘2023年最热门的跨境电商源码趋势,你不能错过的关键信息

随着全球市场的不断扩大和国际贸易的加速&#xff0c;跨境电商源码正成为越来越多企业的首选。在本文中&#xff0c;我们将揭秘2023年跨境电商源码的最新趋势&#xff0c;为您带来关键信息&#xff0c;帮助您抓住机遇&#xff0c;实现商业成功。 2023年跨境电商源码趋势解析 …

JS-项目实战-鼠标悬浮设置字体颜色以及控制键盘输入

1、fruit.js //当页面加载完成后执行后面的匿名函数 window.onload function () {//get:获取 Element:元素 By:通过...方式//getElementById()根据id值获取某元素let fruitTbl document.getElementById("fruit_tbl");//table.rows:获取这个表格的所有的行&a…

Go 使用Viper处理Go应用程序的配置

在开发Go应用程序时&#xff0c;处理配置是一个常见的需求。配置可能来自于配置文件、环境变量、命令行参数等等。Viper是一个强大的库&#xff0c;可以帮助我们处理这些配置。 什么是Viper&#xff1f; Viper是一个应用程序配置解决方案&#xff0c;用于Go应用程序。它支持JS…

推介会如何做好媒体宣传

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 推介会是一种专为企业、社会组织和团体、政府等提供的展示自身特点、产品和政策的活动形式&#xff0c;旨在促进交流活动&#xff0c;形成合作&#xff0c;从而带来共同利益。推介会的本…

ffmpeg 4.4 cenc-aes-ctr 加解密 MP4 工程性质分析

目录 一、cenc-aes-ctr 原理介绍 二、显式 cenc-aes-ctr 和隐式 cenc-aes-ctr 三、加密工具---ffmpeg 四、播放---ffplay 五、总结 ​​​​​​​一、cenc-aes-ctr 原理介绍 加密算法&#xff1a;CENC-AES-CTR 使用 AES&#xff08;Advanced Encryption Standard&…

微服务架构学习与思考

参考&#xff1a;微服务架构学习与思考(01)&#xff1a;什么是微服务&#xff1f;微服务的优势和劣势 - 九卷 - 博客园 (cnblogs.com) 一、单体应用 在软件开发早期阶段&#xff0c;大家都在一个应用系统上开发。各个业务模块之间耦合也比较紧密。软件发布也是整体发布&#…

实验室LIMS系统 asp.net源码 lims系统源码

LIMS系统是以实验室为中心&#xff0c;将人员、仪器、试剂、方法、环境、文件等影响分析数据的因素有机结合&#xff0c;针对实验室的要求&#xff0c;遵循ISO 17025准则&#xff0c;采用先进的计算机网络技术、数据存储技术、快速和强大的数据处理技术来对实验室进行全面管理的…

【备忘】websocket学习之挖坑埋自己

背景故事 以前没有好好学习过websocket&#xff0c;只知道它有什么用途&#xff0c;也知道是个好东西&#xff0c;平时在工作中没用过&#xff0c;所以对它并不知所以然。如今要做个自己的项目&#xff0c;要在付款的时候实时播报声音。自己是个开发者&#xff0c;也不想用别人…

【XTDrone Ubuntu20.04】XTDrone+ Ubuntu20.04 + PX4安装

XTDrone仿真平台配置 文章目录 XTDrone仿真平台配置依赖安装 ROS一键安装Marvos安装PX4 安装安装QTGroundControlXTDrone下载安装 环境&#xff1a; VMWare 16.0 Ubuntu 22.04 &#xff08;因为没人配过&#xff09;Ubuntu 20.04 参考文章&#xff1a; 仿真平台基础配置 (yuq…

还有医学生不知道这个免费好用的在线样本量计算器吗?

相信很多小伙伴都有过这样的经历&#xff1a;做科研设计、撰写论文&#xff0c;设计好主题后摆在眼前的是你最头痛的问题——样本量计算。事实上&#xff0c;样本量计算往往是临床医生做临床研究设计的一大障碍&#xff0c;是临床研究设计、临床知识经验以及统计学知识的结合。…

adguarg通过dns代理全局过滤广告,全系统操作指南

路由器dns配置 安卓(鸿蒙) 设置>>其他网络与连接>>私人DNS&#xff08;不同手机系统设置名称会有些许出入&#xff0c;但是大差不差&#xff09; &#xff08;左图鸿蒙&#xff09;&#xff1a;将域名m.centos.chat填入手机私人DNS IOS系统 将代理服务器IP&am…

React-Router源码分析-History库

history源码 history 在 v5 之前使用单独的包&#xff0c; v6 之后再 router 包中单独实现。 history源码 Action 路由切换的动作类型&#xff0c;包含三种类型&#xff1a; POPREPLACEPUSH Action 枚举&#xff1a; export enum Action {Pop "POP",Push &quo…

核心容器中bean的操作

bean配置 bean基础配置 bean别名配置 **注意事项&#xff1a;**获取bean无论是通过id还是name获取&#xff0c;如果无法获取到&#xff0c;将抛出异常NoSuchBeanDefinitionException&#xff08;NoSuchBeanDefinitionException&#xff1a;No bean named ‘bookServiceImpl’ …

多GPU训练大型模型:资源分配与优化技巧 | 英伟达将推出面向中国的改良芯片HGX H20、L20 PCIe、L2 PCIe

★大模型、人工智能&#xff1b;数据并行&#xff1b;模型并行&#xff1b;流水线并行&#xff1b;混合精度训练、梯度累积&#xff1b;模型卸载CPU&#xff1b;重算&#xff1b;模型压缩&#xff1b;内存优化版优化器&#xff1b;Nvidia&#xff1b;A100;H100&#xff1b;A800…

Nutz框架如何自定义SQL?

Nutz框架基本的简单sql已经封装了&#xff0c;但是一些叫为复杂的sql需要手动去写&#xff0c;那如何实现像Mybatis那样通过配置文件编写呢&#xff1f;如有不明白详见官方文档&#xff1a;自定义 SQL - Nutzhttps://nutzam.com/core/dao/customized_sql.html#ndoc-4 一 新建…

Linux - Namespace

一、namespace 是什么&#xff1f; Linux namespaces 是对全局系统资源的一种封装隔离&#xff0c;使得处于不同 namespace 的进程拥有独立的全局系统资源&#xff0c;改变一个 namespace 中的系统资源只会影响当前 namespace 里的进程&#xff0c;对其他 namespace 中的进程没…

Pikachu漏洞练习平台之XXE(XML外部实体注入)

目录 什么是 XML&#xff1f; 什么是DTD&#xff1f; 什么是XEE&#xff1f; 常见payload 什么是 XML&#xff1f; XML 指可扩展标记语言&#xff08;EXtensible Markup Language&#xff09;&#xff1b; XML 不会做任何事情&#xff0c;而是用来结构化、存储以及传输信息…