图解最短路径之弗洛伊德算法(Java实现)

概述

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

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

算法描述

在有向图 G=(V,E) 中,假设每条边 E[i] 的长度为 w[i],找到V钟任意两点之间的路径长度最小值。

算法流程

本节将对算法流程进行模拟,设置Graph为包含7个顶点和9条边的有向无环图,Graph如下:

弗洛伊德算法选取某个节点k作为i到j需要经过的中间节点,通过比较d(i,k)+d(k,j)和现有d(i,j)的大小,将较小值更新为路径长度,对k节点的选取进行遍历,以得到在经过所有节点时i到j的最短路径长度,通过不断加入中间点的方式更新最短路径。同时在path数组中存储i到j所经过的中间节点k,用于最后递归调用输出路径结果。

算法步骤如下:

1、初始化矩阵。

2、选取0号节点作为中间点,初始化矩阵。

3、选取1号节点作为中间点,更新矩阵,通过两层循环计算(i->1),(1->j)的路径是否比目前i到j的路径长度更短。此时可以将矩阵数值看作是将0、1作为中间点获得的多源最短路径长度。

4、选取2号节点作为中间点,更新矩阵,通过两层循环计算(i->2),(2->j)的路径是否比目前i到j的路径长度更短。此时可以将矩阵数值看作是将0、1、2作为中间点获得的多源最短路径长度。

5、选取3号节点作为中间点,更新矩阵,通过两层循环计算(i->3),(1->3)的路径是否比目前i到j的路径长度更短。此时可以将矩阵数值看作是将0、1、2、3为中间点获得的多源最短路径长度。

6、选取4号节点作为中间点,更新矩阵,通过两层循环计算(i->4),(4->j)的路径是否比目前i到j的路径长度更短。此时可以将矩阵数值看作是将0、1、2、3、4作为中间点获得的多源最短路径长度。

7、选取5号节点作为中间点,更新矩阵,通过两层循环计算(i->5),(5->j)的路径是否比目前i到j的路径长度更短。此时可以将矩阵数值看作是将0、1、2、3、4、5作为中间点获得的多源最短路径长度。

8、选取6号节点作为中间点,更新矩阵,通过两层循环计算(i->6),(6->j)的路径是否比目前i到j的路径长度更短。此时可以将矩阵数值看作是将所有节点作为中间点获得的多源最短路径长度,遍历结束,得到最后结果。

算法实现

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);
    }
}

样例输入:

7 10

0 1 6

1 2 5

0 3 2

3 1 7

3 4 5

1 2 5

1 5 3

5 2 3

5 4 2

4 6 1

Q&A

问:算法中的三层for循环,每一层分别控制什么?

答:第一层循环设置中间点k,第二层循环设置起始点i,第三层循环设置结束点j。

问:为什么弗洛伊德算法支持负权值?

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

### Floyd最短路径算法的图形解释 Floyd-Warshall算法由Robert Floyd于1962年提出,用于解决多源最短路径问题,能够计算出图中任意两点间的最短路径[^1]。此算法采用动态规划的思想,在每次迭代过程中逐渐引入新的中间顶点来优化当前已知的最佳路径。 #### 算法流程概述 假设存在一个加权图G=(V,E),其中V表示顶点集合而E代表边集,则对于每一对顶点(i,j): - 初始化阶段:如果i和j之间直接相连,则设置距离为对应的权重;否则设为无穷大。 - 迭代更新过程:遍历所有可能作为中介节点k∈V的情况,检查是否存在一条经过k使得从i到达j更近的新路经,并据此调整相应的最短路程估计值。 #### Python代码实现与示例展示 下面给出一段简单的Python程序片段用来说明如何利用邻接矩阵形式的数据结构执行上述操作: ```python import numpy as np def floyd_warshall(graph): V = len(graph) dist = list(map(lambda i : map(lambda j : j , i) , graph)) for k in range(V): for i in range(V): for j in range(V): dist[i][j] = min(dist[i][j],dist[i][k]+dist[k][j]) return dist if __name__ == "__main__": INF = float('inf') g=[[0,7,INF,8], [INF,0,5,INF], [INF,INF,0,2], [INF,INF,INF,0]] result=floyd_warshall(g) print("The shortest path matrix is:") for row in result: formatted_row=[str(x).rjust(4,' ')for x in row] print('[{}]'.format(','.join(formatted_row))) ``` 这段脚本定义了一个名为`floyd_warshall()`的功能函数接收输入参数graph——即待处理的有权重无向图(或有向图),并通过三重循环实现了对整个图表内各条线路长度关系的有效评估。最后打印出了最终得到的距离矩阵,直观展现了任意两结点间最短行程的具体数值。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值