zoukankan      html  css  js  c++  java
  • 最短路径问题:Dijkstra算法

    定义

    所谓最短路径问题是指:如果从图中某一顶点(源点)到达另一顶点(终点)的路径可能不止一条,如何找到一条路径使得沿此路径上各边的权值总和(称为路径长度)达到最小。

    下面我们介绍两种比较常用的求最短路径算法:

    Dijkstra(迪杰斯特拉)算法

    他的算法思想是按路径长度递增的次序一步一步并入来求取,是贪心算法的一个应用,用来解决单源点到其余顶点的最短路径问题。

    算法思想

    首先,我们引入一个辅助向量D,它的每个分量D[i]表示当前找到的从起始节点v到终点节点vi的最短路径的长度。它的初始态为:若从节点v到节点vi有弧,则D[i]为弧上的权值,否则D[i]为∞,显然,长度为D[j] = Min{D[i] | vi ∈V}的路径就是从v出发最短的一条路径,路径为(v, vi)。

    那么,下一条长度次短的最短路径是哪一条呢?假设次短路径的终点是vk,则可想而知,这条路径或者是(v, vk)或者是(v, vj, vk)。它的长度或者是从v到vk的弧上的权值,或者是D[j]和从vj到vk的权值之和。

    因此下一条次短的最短路径的长度是:D[j] = Min{D[i] | vi ∈ V - S},其中,D[i]或者是弧(v, vi)的权值,或者是D[k](vk ∈ S)和弧(vk, vi)上权值之和。

    算法描述

    假设现要求取如下示例图所示的顶点V0与其余各顶点的最短路径:

    Dijkstra算法示例图

    我们使用Guava的ValueGraph作为该图的数据结构,每个顶点对应一个visited变量来表示节点是在V中还是在S中,初始时S中只有顶点V0。然后,我们看看新加入的顶点是否可以到达其他顶点,并且看看通过该顶点到达其他点的路径长度是否比从V0直接到达更短,如果是,则修改这些顶点的权值(即if (D[j] + arcs[j][k] < D[k]) then D[k] = D[j] + arcs[j][k])。然后又从{V - S}中找最小值,重复上述动作,直到所有顶点都并入S中。

    第一步,我们通过ValueGraphBuilder构造图的实例,并输入边集:

    MutableValueGraph<String, Integer> graph = ValueGraphBuilder.directed()
            .nodeOrder(ElementOrder.insertion())
            .expectedNodeCount(10)
            .build();
    graph.putEdgeValue(V0, V2, 10);
    graph.putEdgeValue(V0, V4, 30);
    graph.putEdgeValue(V0, V5, 100);
    graph.putEdgeValue(V1, V2, 5);
    graph.putEdgeValue(V2, V3, 50);
    graph.putEdgeValue(V3, V5, 10);
    graph.putEdgeValue(V4, V3, 20);
    graph.putEdgeValue(V4, V5, 60);
    
    return graph;
    

    初始输出结果如下:

    nodes: [v0, v2, v4, v5, v1, v3], 
    edges: {<v0 -> v5>=100, <v0 -> v4>=30, <v0 -> v2>=10, 
    <v2 -> v3>=50, <v4 -> v5>=60, <v4 -> v3>=20, <v1 -> v2>=5, 
    <v3 -> v5>=10}
    

    为了不破坏graph的状态,我们引入一个临时结构来记录每个节点运算的中间结果:

    private static class NodeExtra {
        public String nodeName; //当前的节点名称
        public int distance; //开始点到当前节点的最短路径
        public boolean visited; //当前节点是否已经求的最短路径(S集合)
        public String preNode; //前一个节点名称
        public String path; //路径的所有途径点
    }
    

    第二步,我们首先将起始点V0并入集合S中,因为他的最短路径已知为0:

    startNode = V0;
    NodeExtra current = nodeExtras.get(startNode);
    current.distance = 0; //一开始可设置开始节点的最短路径为0
    current.visited = true; //并入S集合
    current.path = startNode;
    current.preNode = startNode;
    

    第三步,在当前状态下找出起始点V0开始到其他节点路径最短的节点:

    NodeExtra minExtra = null; //路径最短的节点信息
    int min = Integer.MAX_VALUE;
    for (String notVisitedNode : nodes) {
        //获取节点的辅助信息
        NodeExtra extra = nodeExtras.get(notVisitedNode); 
        
        //不在S集合中,且路径较短
        if (!extra.visited && extra.distance < min) {
            min = extra.distance;
            minExtra = extra;
        }
    }
    

    第四步,将最短路径的节点并入集合S中:

    if (minExtra != null) { //找到了路径最短的节点
        minExtra.visited = true; //并入集合S中
        //更新其中转节点路径
        minExtra.path = nodeExtras.get(minExtra.preNode).path + " -> " + minExtra.nodeName; 
        current = minExtra; //标识当前并入的最短路径节点
    }
    

    第五步,更新与其相关节点的最短路径中间结果:

    /**
     * 并入新查找到的节点后,更新与其相关节点的最短路径中间结果
     * if (D[j] + arcs[j][k] < D[k]) D[k] = D[j] + arcs[j][k]
     */
    //只需循环当前节点的后继列表即可(优化)
    Set<String> successors = graph.successors(current.nodeName); 
    for (String notVisitedNode : successors) {
        NodeExtra extra = nodeExtras.get(notVisitedNode);
        if (!extra.visited) {
            final int value = current.distance 
                + graph.edgeValueOrDefault(current.nodeName,
                    notVisitedNode, 0); //D[j] + arcs[j][k]
            if (value < extra.distance) { //D[j] + arcs[j][k] < D[k]
                extra.distance = value;
                extra.preNode = current.nodeName;
            }
        }
    }
    

    第六步,输出起始节点V0到每个节点的最短路径以及路径的途径点信息

    Set<String> keys = nodeExtras.keySet();
    for (String node : keys) {
        NodeExtra extra = nodeExtras.get(node);
        if (extra.distance < Integer.MAX_VALUE) {
            Log.i(TAG, startNode + " -> " + node + ": min: " + extra.distance
                    + ", path: " + extra.path); //path在运算过程中更新
        }
    }
    

    实例图的输出结果为:

     v0 -> v0: min: 0, path: v0
     v0 -> v2: min: 10, path: v0 -> v2
     v0 -> v3: min: 50, path: v0 -> v4 -> v3
     v0 -> v4: min: 30, path: v0 -> v4
     v0 -> v5: min: 60, path: v0 -> v4 -> v3 -> v5
    

    具体Dijkstra算法的示例demo实现,请参考:

    https://github.com/Jarrywell/GH-Demo/blob/master/app/src/main/java/com/android/test/demo/graph/Dijkstra.java

  • 相关阅读:
    Bypass WAF
    一种简单的hook方法--LD_PRELOAD变量
    Linux C:access()时间条件竞争漏洞
    环境变量法提权
    sudo-tcpdump提权法
    asynico转载
    pychar 2020.1.2激活
    临时mysql 链接池
    python pip 使用阿里云镜像安装库
    zookeeper kafaka 临时保存
  • 原文地址:https://www.cnblogs.com/linhaostudy/p/12133632.html
Copyright © 2011-2022 走看看