数据结构(Java)最短路径(迪杰斯特拉、弗洛伊德算法)

一、问题引入

       

1) 胜利乡有 村庄 (A, B, C, D, E, F, G,H,I,J) ,现在有 9 个邮差,从 G 点出发,需要分别把邮件分别送到 A, B, C , D, E, F ,H,I,J 9个村庄。
2) 各个村庄的距离用边线表示 ( ) ,比如 A – B 距离 5 公里
3) 问:如何计算出 G 村庄到 其它各个村庄的最短距离 ?
4) 如果从其它点出发到各个点的最短距离又是多少 ?

 二、迪杰斯特拉算法Dijkstra

        迪杰斯特拉(Dijkstra)算法是典型最短路径算法,用于计算一个结点到其他结点的最短路径。 它的主要特点是以起始点为中心向外层层扩展(广度优先搜索思想),直到扩展到终点为止

        设置出发顶点为v,顶点集合V{v1,v2,vi...}vV中各顶点的距离构成距离集合DisDis{d1,d2,di...}Dis集合记录着v到图中各顶点的距离(到自身可以看作0vvi距离对应为di)

1) Dis 中选择值最小的 di 并移出 Dis 集合,同时移出 V 集合中对应的顶点 vi ,此时的 v vi 即为最短路径
2) 更新 Dis 集合,更新规则为:比较 v V 集合中顶点的距离值,与 v 通过 vi V 集合中顶点的距离值,保留值较小的一个 ( 同时也应该更新顶点的前驱节点为 vi ,表明是通过 vi 到达的 ) 
3) 重复执行两步骤,直到最短路径顶点为目标顶点即可结束

算法的通俗理解

        1.先定义 距离数组已访问数组,并且初始化:与 G 点相关的权值照常填进去,而不关联的权值都设为极大值,如 65536 ,用于后面比较大小。注:G 到它本身的距离应始终设为0

2.从 距离数组 中挑出 权值最小未被访问的点 , 更新距离数组和已访问数组。这里是重点!
        2.1 用一个变量保存该点到其它点的权值。这个变量是临时的!!
        2.2 用该点到其它点的权值 加上出发点到该点的权值。
        2.3 比较相加后的权值与原来的大小,取 较小的一个,更新到距离数组中。
        2.4 标记已访问
        *比如G是出发点,则 G->A ,而 A->C ,那么就可以 把 A 当做中介 ,求出 G->C,进而更新从G出发到与A关联的所有点的距离。这就是一个动态规划的过程。
3. 重复 2 直到所有点都被访问

三、弗洛依德算法Floyd

        弗洛伊德算法 VS 迪杰斯特拉算法:迪杰斯特拉算法通过选定的被访问顶点,求出从出发访问顶点到其他顶点的最短路径;弗洛伊德算法中每一个顶点都是出发访问点,所以需要将每一个顶点看做被访问顶点,求出从每一个顶点到其他顶点的最短路径

弗洛伊德(Floyd)算法图解分析

1) 设置顶点 vi 到顶点 vk 的最短路径已知为 Lik ,顶点 vk vj 的最短路径已知为 Lkj ,顶点 vi vj 的路径为 Lij ,则 vi vj 的最短路径为: min((Lik+Lkj),Lij) vk 的取值为图中所有顶点,则可获得 vi vj 的最短路径
2) 至于 vi vk 的最短路径 Lik 或者 vk vj 的最短路径 Lkj ,是以同样的方式获得
通俗理解
        例如A、B、C三个点, B 是 A、C的中间顶点。 A->CA->B->C 都可行,那就对比两者路径长度,取 较小的一个作为 A->B 的最短路径。
        采用三个循环,外循环为 中间顶点,内循环分别为 出发点终点。不断更新 距离数组就行了(这里是二维数组)。这样最后就可以得到 任意两点的最短距离。

四、代码演示

 4.1 图的定义

class Graph {
    protected List<String> vertex;//存放顶点
    protected int[][] edges;//存放边
    protected boolean[] isVisited;//是否被访问
    protected int numOfEdges;

    public Graph(int n) {
        this.vertex = new ArrayList<>(n);
        this.edges = new int[n][n];
        this.isVisited = new boolean[n];
    }

    //常用方法
    // 1. 获取节点个数
    protected int getNumOfVertex() {
        return vertex.size();
    }

    // 2. 打印邻接矩阵
    protected void printGraph() {
        System.out.print(" ");
        for (String s : vertex) System.out.print("     " + s);
        System.out.println();
        for (int r = 0; r < vertex.size(); r++) {
            System.out.print(vertex.get(r) + " ");
            for (int c = 0; c < vertex.size(); c++) {
                System.out.print(String.format("%5d",edges[r][c]) + " ");
            }
            System.out.println();
        }
        System.out.println();
    }

    // 3. 获取边的数目
    protected int getNumOfEdges() {
        return numOfEdges;
    }

    // 4. 获取某条边的权值
    protected int getWeightOfEdges(int v1, int v2) {
        return edges[v1][v2];
    }

    // 5. 添加节点
    protected void addVertex(String v) {
        vertex.add(v);
    }

    // 6. 添加边(双向)
    protected void addEdge(int v1, int v2, int weight) {
        edges[v1][v2] = weight;
        edges[v2][v1] = weight;
        numOfEdges++;
    }

    // 7.获取顶点索引对应的值
    protected String getValueByIndex(int i) {
        return vertex.get(i);
    }
}

4.2 迪杰斯特拉算法求最短路径

 //*****************************迪杰斯特拉算法*********************************************
    public static void Dijkstra(Graph graph, int v) {
        int[] distance = new int[graph.getNumOfVertex()];//顶点v到其他各顶点的距离
        int[] visited = new int[graph.getNumOfVertex()];//存储中间的遍历结果
        // 一、初始化
        init(v, distance, visited, graph);
        // 二、循环更新距离直到所有点都被遍历
        while (!ifEnd(visited)) {
            update(getMinDistanceIndex(distance, visited, v), visited, distance, graph, v);
        }
    }

    // 1.初始化(初始顶点需特殊处理)
    public static void init(int beginIndex, int[] distance, int[] visited, Graph graph) {
        for (int i = 0; i < distance.length; i++) {
            if (graph.edges[beginIndex][i] != 0) distance[i] = graph.edges[beginIndex][i];//先获得原始的距离数组
        }
        distance[beginIndex] = 0;//距起始点的距离始终为0,后面更新时要额外注意避免修改
        visited[beginIndex] = 1;//起始点标记已访问
        System.out.println("初始化...................");
        System.out.println("距离:");
        System.out.println(Arrays.toString(distance));
        System.out.println("已访问顶点:");
        System.out.println(Arrays.toString(visited));
        System.out.println();
    }
    // 2.返回distance中距离最短并未被访问的顶点索引
    public static int getMinDistanceIndex(int[] distance, int[] visited, int v) {
        int minDis = 65536;//假定最小值
        int minDisIndex = 0;
        for (int i = 0; i < distance.length; i++) {
            if (distance[i] < minDis && i != v && visited[i] == 0) {//要找未访问的点,同时不可以是出发点!
                minDis = distance[i];
                minDisIndex = i;
            }
        }
        return minDisIndex;
    }

    // 3.更新操作,每次都更新距离数组和已访问数组(重点难点在于更新距离)
    public static void update(int index, int[] visited, int[] distance, Graph graph, int v) {
        Map<String,String> modifiedVertex=new HashMap<>();
        int[] tempDis = new int[distance.length];
        //首先把index对应点与其他点的距离保存在临时变量中
        for (int i = 0; i < graph.getNumOfVertex(); i++) {
            if (graph.edges[index][i] != 0) {
                tempDis[i] = graph.edges[index][i];
            }
        }
        //修改距离,要加上从出发点到index顶点的距离。注意原始出发点不能动!
        for (int k = 0; k < graph.getNumOfVertex(); k++) {
            if (tempDis[k] != 65536 && k != v) {
                tempDis[k] += distance[index];
            }
        }
        //修改后的距离如果比原来的小,就更新distance,同样不能动原始出发点!(感觉这里像是动态规划的思想,需要动态调整到所有点的距离)
        for (int j = 0; j < graph.getNumOfVertex(); j++) {
            if (tempDis[j] < distance[j] && j != v) {
                modifiedVertex.put(graph.getValueByIndex(j),distance[j]+"->"+tempDis[j]);
                distance[j] = tempDis[j];
            }
        }
        //标记这个点已访问
        visited[index] = 1;
        //输出本次的更新结果
        System.out.println();
        System.out.print("距离: ");
        System.out.println(Arrays.toString(distance));
        System.out.print("更新距离: ");
        if(modifiedVertex.isEmpty()) System.out.print("本次未更新");
        else for(Map.Entry<String,String>entry:modifiedVertex.entrySet())
            System.out.print(entry.getKey()+":"+entry.getValue()+" ");
        System.out.println();
        System.out.print("已访问顶点: ");
        System.out.println(Arrays.toString(visited));
    }

    // 4.判断已访问数组是否满,满了就结束
    public static Boolean ifEnd(int[] visited) {
        for (int i = 0; i < visited.length; i++) {
            if (visited[i] == 0) {
                return false;
            }
        }
        return true;
    }

 测试:

 @Test
    public void testDijkstra() {
        Graph graph = new Graph(10);
        graph.addVertex("A");
        graph.addVertex("B");
        graph.addVertex("C");
        graph.addVertex("D");
        graph.addVertex("E");
        graph.addVertex("F");
        graph.addVertex("G");
        graph.addVertex("H");
        graph.addVertex("I");
        graph.addVertex("J");
        graph.addEdge(0, 1, 5);
        graph.addEdge(0, 2, 7);
        graph.addEdge(0, 6, 2);
        graph.addEdge(1, 6, 3);
        graph.addEdge(1, 3, 9);
        graph.addEdge(2, 4, 8);
        graph.addEdge(3, 5, 4);
        graph.addEdge(4, 5, 5);
        graph.addEdge(4, 6, 4);
        graph.addEdge(5, 6, 6);
        graph.addEdge(4, 7, 5);
        graph.addEdge(5, 7, 5);
        graph.addEdge(5, 8, 4);
        graph.addEdge(7, 8, 3);
        graph.addEdge(3, 9, 6);
        graph.addEdge(8, 9, 2);
        //权为0的边权都等于65536.
        for (int i = 0; i < graph.getNumOfVertex(); i++) {
            for (int j = 0; j < graph.getNumOfVertex(); j++) {
                if (graph.edges[i][j] == 0) graph.edges[i][j] = 65536;
            }
        }
        System.out.println("边的数量: " + graph.getNumOfEdges());
        System.out.println("顶点的数量: " + graph.getNumOfVertex());
        System.out.println("邻接矩阵:");
        graph.printGraph();
        minRoute.Dijkstra(graph, 6);
    }



边的数量: 16
顶点的数量: 10
邻接矩阵:
      A     B     C     D     E     F     G     H     I     J
A 65536     5     7 65536 65536 65536     2 65536 65536 65536 
B     5 65536 65536     9 65536 65536     3 65536 65536 65536 
C     7 65536 65536 65536     8 65536 65536 65536 65536 65536 
D 65536     9 65536 65536 65536     4 65536 65536 65536     6 
E 65536 65536     8 65536 65536     5     4     5 65536 65536 
F 65536 65536 65536     4     5 65536     6     5     4 65536 
G     2     3 65536 65536     4     6 65536 65536 65536 65536 
H 65536 65536 65536 65536     5     5 65536 65536     3 65536 
I 65536 65536 65536 65536 65536     4 65536     3 65536     2 
J 65536 65536 65536     6 65536 65536 65536 65536     2 65536 

初始化...................
距离:
[2, 3, 65536, 65536, 4, 6, 0, 65536, 65536, 65536]
已访问顶点:
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0]


距离: [2, 3, 9, 65536, 4, 6, 0, 65536, 65536, 65536]
更新距离: C:65536->9 
已访问顶点: [1, 0, 0, 0, 0, 0, 1, 0, 0, 0]

距离: [2, 3, 9, 12, 4, 6, 0, 65536, 65536, 65536]
更新距离: D:65536->12 
已访问顶点: [1, 1, 0, 0, 0, 0, 1, 0, 0, 0]

距离: [2, 3, 9, 12, 4, 6, 0, 9, 65536, 65536]
更新距离: H:65536->9 
已访问顶点: [1, 1, 0, 0, 1, 0, 1, 0, 0, 0]

距离: [2, 3, 9, 10, 4, 6, 0, 9, 10, 65536]
更新距离: D:12->10 I:65536->10 
已访问顶点: [1, 1, 0, 0, 1, 1, 1, 0, 0, 0]

距离: [2, 3, 9, 10, 4, 6, 0, 9, 10, 65536]
更新距离: 本次未更新
已访问顶点: [1, 1, 1, 0, 1, 1, 1, 0, 0, 0]

距离: [2, 3, 9, 10, 4, 6, 0, 9, 10, 65536]
更新距离: 本次未更新
已访问顶点: [1, 1, 1, 0, 1, 1, 1, 1, 0, 0]

距离: [2, 3, 9, 10, 4, 6, 0, 9, 10, 16]
更新距离: J:65536->16 
已访问顶点: [1, 1, 1, 1, 1, 1, 1, 1, 0, 0]

距离: [2, 3, 9, 10, 4, 6, 0, 9, 10, 12]
更新距离: J:16->12 
已访问顶点: [1, 1, 1, 1, 1, 1, 1, 1, 1, 0]

距离: [2, 3, 9, 10, 4, 6, 0, 9, 10, 12]
更新距离: 本次未更新
已访问顶点: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

 4.3 弗洛伊德算法求最短路径

 //*****************************弗洛伊德算法*********************************************
    public static void Floyd(Graph graph) {
        int[][] distance = graph.edges;//记录各顶点间的距离
        // mid-中间顶点  begin-起始顶点  end-终点顶点
        for(int mid=0;mid<graph.getNumOfVertex();mid++) {
            System.out.println("\n"+graph.getValueByIndex(mid)+"作为中间顶点...");
            for (int begin = 0; begin < graph.getNumOfVertex(); begin++) {
                for(int end = 0; end < graph.getNumOfVertex(); end++) {
                    //对于两个顶点A、B,以及他们中间的顶点C,
                    // 如果A->C->B 的距离比 A->B 的距离短,就更新A、B间的距离
                    int newDis=distance[begin][mid]+distance[mid][end];
                    if(distance[begin][end]>newDis && begin!=end){
                        System.out.println("修改距离:"+graph.getValueByIndex(begin)+"->"+graph.getValueByIndex(end)
                                +" "+distance[begin][end]+", 修改为 :"+newDis);
                        distance[begin][end]=newDis;
                        distance[end][begin]=newDis;//更新距离,对于无向图 Lij=Lji
                    }
                }
            }
        }
        //结果展示
        System.out.println(" "+"各个顶点间的最终最短距离:");
        for(int i=0;i<graph.getNumOfVertex();i++){
            for(int j=0;j<graph.getNumOfVertex();j++){
                if(distance[i][j]!=65536) System.out.print(" "+graph.getValueByIndex(i)+"->"+graph.getValueByIndex(j)+" "+distance[i][j]+",");
            }
            System.out.println("\n");
        }
    }

测试:

 @Test
    public void testFloyd() {
        Graph graph = new Graph(10);
        graph.addVertex("A");
        graph.addVertex("B");
        graph.addVertex("C");
        graph.addVertex("D");
        graph.addVertex("E");
        graph.addVertex("F");
        graph.addVertex("G");
        graph.addVertex("H");
        graph.addVertex("I");
        graph.addVertex("J");
        graph.addEdge(0, 1, 5);
        graph.addEdge(0, 2, 7);
        graph.addEdge(0, 6, 2);
        graph.addEdge(1, 6, 3);
        graph.addEdge(1, 3, 9);
        graph.addEdge(2, 4, 8);
        graph.addEdge(3, 5, 4);
        graph.addEdge(4, 5, 5);
        graph.addEdge(4, 6, 4);
        graph.addEdge(5, 6, 6);
        graph.addEdge(4, 7, 5);
        graph.addEdge(5, 7, 5);
        graph.addEdge(5, 8, 4);
        graph.addEdge(7, 8, 3);
        graph.addEdge(3, 9, 6);
        graph.addEdge(8, 9, 2);
        //权为0的边权都等于65536.
        for (int i = 0; i < graph.getNumOfVertex(); i++) {
            for (int j = 0; j < graph.getNumOfVertex(); j++) {
                if (graph.edges[i][j] == 0) graph.edges[i][j] = 65536;
            }
        }
        System.out.println("边的数量: " + graph.getNumOfEdges());
        System.out.println("顶点的数量: " + graph.getNumOfVertex());
        System.out.println("邻接矩阵:");
        graph.printGraph();
        minRoute_.Floyd(graph);
    }



边的数量: 16
顶点的数量: 10
邻接矩阵:
      A     B     C     D     E     F     G     H     I     J
A 65536     5     7 65536 65536 65536     2 65536 65536 65536 
B     5 65536 65536     9 65536 65536     3 65536 65536 65536 
C     7 65536 65536 65536     8 65536 65536 65536 65536 65536 
D 65536     9 65536 65536 65536     4 65536 65536 65536     6 
E 65536 65536     8 65536 65536     5     4     5 65536 65536 
F 65536 65536 65536     4     5 65536     6     5     4 65536 
G     2     3 65536 65536     4     6 65536 65536 65536 65536 
H 65536 65536 65536 65536     5     5 65536 65536     3 65536 
I 65536 65536 65536 65536 65536     4 65536     3 65536     2 
J 65536 65536 65536     6 65536 65536 65536 65536     2 65536 


A作为中间顶点...
修改距离:B->C 65536, 修改为 :12
修改距离:C->G 65536, 修改为 :9

B作为中间顶点...
修改距离:A->D 65536, 修改为 :14
修改距离:C->D 65536, 修改为 :21
修改距离:D->G 65536, 修改为 :12

C作为中间顶点...
修改距离:A->E 65536, 修改为 :15
修改距离:B->E 65536, 修改为 :20
修改距离:D->E 65536, 修改为 :29

D作为中间顶点...
修改距离:A->F 65536, 修改为 :18
修改距离:A->J 65536, 修改为 :20
修改距离:B->F 65536, 修改为 :13
修改距离:B->J 65536, 修改为 :15
修改距离:C->F 65536, 修改为 :25
修改距离:C->J 65536, 修改为 :27
修改距离:E->J 65536, 修改为 :35
修改距离:F->J 65536, 修改为 :10
修改距离:G->J 65536, 修改为 :18

E作为中间顶点...
修改距离:A->H 65536, 修改为 :20
修改距离:B->H 65536, 修改为 :25
修改距离:C->F 25, 修改为 :13
修改距离:C->H 65536, 修改为 :13
修改距离:D->H 65536, 修改为 :34
修改距离:G->H 65536, 修改为 :9
修改距离:H->J 65536, 修改为 :40

F作为中间顶点...
修改距离:A->I 65536, 修改为 :22
修改距离:B->E 20, 修改为 :18
修改距离:B->H 25, 修改为 :18
修改距离:B->I 65536, 修改为 :17
修改距离:C->D 21, 修改为 :17
修改距离:C->I 65536, 修改为 :17
修改距离:C->J 27, 修改为 :23
修改距离:D->E 29, 修改为 :9
修改距离:D->G 12, 修改为 :10
修改距离:D->H 34, 修改为 :9
修改距离:D->I 65536, 修改为 :8
修改距离:E->I 65536, 修改为 :9
修改距离:E->J 35, 修改为 :15
修改距离:G->I 65536, 修改为 :10
修改距离:G->J 18, 修改为 :16
修改距离:H->J 40, 修改为 :15

G作为中间顶点...
修改距离:A->D 14, 修改为 :12
修改距离:A->E 15, 修改为 :6
修改距离:A->F 18, 修改为 :8
修改距离:A->H 20, 修改为 :11
修改距离:A->I 22, 修改为 :12
修改距离:A->J 20, 修改为 :18
修改距离:B->E 18, 修改为 :7
修改距离:B->F 13, 修改为 :9
修改距离:B->H 18, 修改为 :12
修改距离:B->I 17, 修改为 :13

H作为中间顶点...
修改距离:C->I 17, 修改为 :16
修改距离:E->I 9, 修改为 :8

I作为中间顶点...
修改距离:A->J 18, 修改为 :14
修改距离:C->J 23, 修改为 :18
修改距离:E->J 15, 修改为 :10
修改距离:F->J 10, 修改为 :6
修改距离:G->J 16, 修改为 :12
修改距离:H->J 15, 修改为 :5

J作为中间顶点...
 各个顶点间的最终最短距离:
 A->B 5, A->C 7, A->D 12, A->E 6, A->F 8, A->G 2, A->H 11, A->I 12, A->J 14,

 B->A 5, B->C 12, B->D 9, B->E 7, B->F 9, B->G 3, B->H 12, B->I 13, B->J 15,

 C->A 7, C->B 12, C->D 17, C->E 8, C->F 13, C->G 9, C->H 13, C->I 16, C->J 18,

 D->A 12, D->B 9, D->C 17, D->E 9, D->F 4, D->G 10, D->H 9, D->I 8, D->J 6,

 E->A 6, E->B 7, E->C 8, E->D 9, E->F 5, E->G 4, E->H 5, E->I 8, E->J 10,

 F->A 8, F->B 9, F->C 13, F->D 4, F->E 5, F->G 6, F->H 5, F->I 4, F->J 6,

 G->A 2, G->B 3, G->C 9, G->D 10, G->E 4, G->F 6, G->H 9, G->I 10, G->J 12,

 H->A 11, H->B 12, H->C 13, H->D 9, H->E 5, H->F 5, H->G 9, H->I 3, H->J 5,

 I->A 12, I->B 13, I->C 16, I->D 8, I->E 8, I->F 4, I->G 10, I->H 3, I->J 2,

 J->A 14, J->B 15, J->C 18, J->D 6, J->E 10, J->F 6, J->G 12, J->H 5, J->I 2,

                                                                      以G点为例

五、总结

         迪杰斯特拉算法的关键是更新距离数组 distance[ ] ,每个遍历到的点相当于 一个中介,利用它到起始点的距离来更新与之邻接其他点起始点的距离。

        弗洛伊德算法最容易理解,把某点当做中间顶点,更新以它为中介两个点间的距离,也是更新 distance[][] 数组。

        两个算法用到的 distance数组的区别在于一维的只记录 一个点到其它点的距离,而二维的记录的是所有点间的距离。

  • 5
    点赞
  • 54
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值