最短路径问题---Floyd算法详解

Floyd算法详解
本文详细介绍Floyd算法,一种解决任意两点间最短路径问题的有效算法。适用于有向图或负权边的情况,同时可用于计算有向图的传递闭包。

参考: https://blog.youkuaiyun.com/qq_35644234/article/details/60875818

前言

Genius only means hard-working all one’s life.

Name:Willam

Time:2017/3/8

1、最短路径问题介绍

问题解释:
从图中的某个顶点出发到达另外一个顶点的所经过的边的权重和最小的一条路径,称为最短路径

解决问题的算法:

之前已经对Dijkstra算法做了介绍(不懂的可以看这篇博客:Dijkstra算法详解),所以这篇博客打算对Floyd算法做详细的的介绍。

2、Floyd算法的介绍

  • 算法的特点:
    弗洛伊德算法是解决任意两点间的最短路径的一种算法,可以正确处理有向图或有向图或负权(但不可存在负权回路)的最短路径问题,同时也被用于计算有向图的传递闭包。

  • 算法的思路

通过Floyd计算图G=(V,E)中各个顶点的最短路径时,需要引入两个矩阵,矩阵S中的元素a[i][j]表示顶点i(第i个顶点)到顶点j(第j个顶点)的距离。矩阵P中的元素b[i][j],表示顶点i到顶点j经过了b[i][j]记录的值所表示的顶点。

假设图G中顶点个数为N,则需要对矩阵D和矩阵P进行N次更新。初始时,矩阵D中顶点a[i][j]的距离为顶点i到顶点j的权值;如果i和j不相邻,则a[i][j]=∞,矩阵P的值为顶点b[i][j]的j的值。 接下来开始,对矩阵D进行N次更新。第1次更新时,如果”a[i][j]的距离” > “a[i][0]+a[0][j]”(a[i][0]+a[0][j]表示”i与j之间经过第1个顶点的距离”),则更新a[i][j]为”a[i][0]+a[0][j]”,更新b[i][j]=b[i][0]。 同理,第k次更新时,如果”a[i][j]的距离” > “a[i][k-1]+a[k-1][j]”,则更新a[i][j]为”a[i][k-1]+a[k-1][j]”,b[i][j]=b[i][k-1]。更新N次之后,操作完成!

3、Floyd算法的实例过程

上面,我们已经介绍了算法的思路,如果,你觉得还是不理解,那么通过一个实际的例子,把算法的过程过一遍,你就明白了,如下图,我们求下图的每个点对之间的最短路径的过程如下:

这里写图片描述

第一步,我们先初始化两个矩阵,得到下图两个矩阵:
这里写图片描述

这里写图片描述

第二步,以v1为中阶,更新两个矩阵:
发现,a[1][0]+a[0][6] < a[1][6] 和a[6][0]+a[0][1] < a[6][1],所以我们只需要矩阵D和矩阵P,结果如下:

这里写图片描述

这里写图片描述

通过矩阵P,我发现v2–v7的最短路径是:v2–v1–v7

第三步:以v2作为中介,来更新我们的两个矩阵,使用同样的原理,扫描整个矩阵,得到如下图的结果:

这里写图片描述
这里写图片描述

OK,到这里我们也就应该明白Floyd算法是如何工作的了,他每次都会选择一个中介点,然后,遍历整个矩阵,查找需要更新的值,下面还剩下五步,就不继续演示下去了,理解了方法,我们就可以写代码了。

4、Floyd算法的代码实现

package algorithm;
 
/**
 * 弗洛伊德算法思想:
 * 	Ak(i,j)意思是i点到j点经过一系列点,但是点下标最多不超过k
 * 情况1:如果Ak(i,j)不经过k,那么Ak(i,j)=Ak-1(i,j);
 * 情况2:如果Ak(i,j)经过k,那么Ak(i,j)=Ak-1(i,k)+Ak-1(k,j);
 * 	所以Ak(i,j) = min{Ak-1(i,j),Ak-1(i,k)+Ak-1(k,j)};
 * 
 * 第k层的计算依赖于k-1的结果,所以循环从k=0开始计算起
 *
 */
public class FloydAlgorithm {
	private int[][] matrix;  // 用邻接矩阵来表示图
    private char[]  nodeNames;  // 每个点的标号对应一个字符,代表点的名字
    private final int INF = Integer.MAX_VALUE;  
    public FloydAlgorithm(char[] nodeNames, int[][] matrix){  
        this.nodeNames = nodeNames;  
        this.matrix = matrix;  
    }  
    
    public void floyd(){ // 计算出任意两点间的最短路径,只要该路径存在
        int vertexNum = nodeNames.length;
        int[][] distance = new int[vertexNum][vertexNum];// 距离矩阵  
        StringBuilder[][] path = new StringBuilder[vertexNum][vertexNum];//用来存储最短路径经过的点
        // 初始化距离矩阵  
        for(int i=0; i<vertexNum; i++){  
            for(int j=0; j<vertexNum; j++){
            	path[i][j] = new StringBuilder();
                distance[i][j] = matrix[i][j]; 
                if(distance[i][j]!=0) { // 权重>0代表该边存在 初始化它的路径
                	 path[i][j].append(nodeNames[i]+"->"+nodeNames[j]);
                }
            }  
        }  
        //循环更新矩阵的值  最外层循环代表经过的点下标不超过k时的最短路径
        for(int k=0; k<vertexNum; k++){ 
        	// 内嵌的双层循环 代表计算
        	for(int i = 0;i< vertexNum;i++) {
        		if(i==k) {
        			continue;
        		}
        		for(int j = 0;j<vertexNum;j++) {
            		//Ak(i,j) = min{Ak-1(i,j),Ak-1(i,k)+Ak-1(k,j)};Ak-1(i,j)即上一次循环计算出的disance[i][j]
        			if(j!=k&&i!=j) {
        				int ak_1ij = distance[i][j],ak_1ik_plus_ak_1kj  = INF;
        				if(distance[i][k]!=0&&distance[k][j]!=0) { // 必须保证distance[i][k]和distance[k][j]是存在的
        					ak_1ik_plus_ak_1kj = distance[i][k]+distance[k][j];
        				}
        				if(distance[i][j]==0&&ak_1ik_plus_ak_1kj==INF) { // 如果之前i和j之间没有可走的路径
        					continue;
        				}
        				distance[i][j] = ak_1ij > ak_1ik_plus_ak_1kj||ak_1ij==0?ak_1ik_plus_ak_1kj:ak_1ij;//比较求出最短ak(i,j)
        				if(distance[i][j]==ak_1ik_plus_ak_1kj) { //处理路径字符串的拼接
        					path[i][j].delete(0,path.length+1).append(path[i][k].toString().substring(0, path[i][k].length()-1)+path[k][j].toString());
        					// 之前出现C->B:BC->D->B这种状况是因为之前的Stringbuilder没有清空导致的
        				}
        			}
            	}
        	}
        }
        
        System.out.printf("原矩阵: \n");  
        for (int i = 0; i < vertexNum; i++) {  
            for (int j = 0; j < vertexNum; j++)  
                System.out.printf("%02d ",matrix[i][j]);  
            System.out.printf("\n");  
        } 
        // 打印floyd最短路径的结果  
        System.out.printf("最短距离矩阵: \n");  
        for (int i = 0; i < vertexNum; i++) {  
            for (int j = 0; j < vertexNum; j++)  
                System.out.printf("%02d ",distance[i][j]);  
            System.out.printf("\n");  
        }
        
        System.out.printf("最短路径: \n");  
        for (int i = 0; i < vertexNum; i++) {  
            for (int j = 0; j < vertexNum; j++)  {
                if(i==j||distance[i][j]==0) continue;
                System.out.println(nodeNames[i]+"->"+nodeNames[j]+"的最短路径为: "+path[i][j]);
            }
        } 
    }
    
    public static void main(String[] args) {
		long start = System.currentTimeMillis();
    	int[][] graph = new int[6][6];
		char[] nodeNames = new char[] {'A','B','C','D','E','F'};
	   	graph[0][1] = 50;
	    graph[0][2] = 10;
	    graph[0][4] = 45;
	    graph[1][2] = 15;
	    graph[1][4] = 10;
	    graph[2][0] = 20;
	    graph[2][3] = 15;
	    graph[3][1] = 20;
	    graph[3][4] = 35;
	    graph[4][3] = 30;
	    graph[5][3] = 3;
	    
	 FloydAlgorithm floydAlgorithm = new FloydAlgorithm(nodeNames, graph);
	 floydAlgorithm.floyd();
	 long end = System.currentTimeMillis();
	 System.out.println("程序用时:"+(end-start)/1000.0+"秒");
	}
}

输出结果:

原矩阵: 
00 50 10 00 45 00 
00 00 15 00 10 00 
20 00 00 15 00 00 
00 20 00 00 35 00 
00 00 00 30 00 00 
00 00 00 03 00 00 
最短距离矩阵: 
00 45 10 25 45 00 
35 00 15 30 10 00 
20 35 00 15 45 00 
55 20 35 00 30 00 
85 50 65 30 00 00 
58 23 38 03 33 00 
最短路径: 
A->B的最短路径为: A->C->D->B
A->C的最短路径为: A->C
A->D的最短路径为: A->C->D
A->E的最短路径为: A->E
B->A的最短路径为: B->C->A
B->C的最短路径为: B->C
B->D的最短路径为: B->C->D
B->E的最短路径为: B->E
C->A的最短路径为: C->A
C->B的最短路径为: C->D->B
C->D的最短路径为: C->D
C->E的最短路径为: C->D->B->E
D->A的最短路径为: D->B->C->A
D->B的最短路径为: D->B
D->C的最短路径为: D->B->C
D->E的最短路径为: D->B->E
E->A的最短路径为: E->D->B->C->A
E->B的最短路径为: E->D->B
E->C的最短路径为: E->D->B->C
E->D的最短路径为: E->D
F->A的最短路径为: F->D->B->C->A
F->B的最短路径为: F->D->B
F->C的最短路径为: F->D->B->C
F->D的最短路径为: F->D
F->E的最短路径为: F->D->B->E
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值