一个码农在工位上写代码累了,趴着睡着了。
再次睁开眼睛,发现身边好几个妖艳宫女正在给你按摩敲背,住的屋子墙壁和天花板都镶嵌着金箔和银片,大殿的柱子是金丝楠木,雕龙刻凤,地毯是波斯纯手工制作,踩上去柔软而温暖。
突然一个老太监急急忙忙的跑到我跟前说:
“大皇子,皇上驾崩了,得速速从南京回北京继承皇位呀!还有一个重要情报,二皇子已经在南京出发了,意图不轨!”
“这还了得!快拿地图来!”
老太监,缓缓把地图展开,你看着这个图,陷入深深的思考,你需要找到最快的路径

需求:
不考虑路径长短,默认都是1,只考虑节点之间最短的。

初级求解

采用广度优先的思路:

南京 ——> 郑州 ——> 北京 = 3
南京 ——> 郑州 ——> 天津 ——> 北京 = 4
南京 ——> 郑州 ——> 天津 ——> 济南 (死路)
南京 ——> 济南 ——> 天津 ——> 北京 = 4

一眼就知道选3。 当然这个是一个简单图结构,如果是一个复杂的图结构,那么就需要计算机了,计算机是怎么计算图的最短路径呢? 需要了解一下Dijkstra算法。

Dijkstra算法入门

戴克斯特拉算法(英语:Dijkstra's algorithm),又称迪杰斯特拉算法、Dijkstra算法。
迪杰斯特拉(Dijkstra)算法是典型最短路径算法,用于计算一个节点到其他节点的最短路径。

首先设立原点A,目前已知原点A点至A点的距离为0,将其记录在原点上,标记为已探索,其余顶点尚未探索因此皆在该点上标记为为无穷大(∞)。

运行原理:

  1. 指定一个节点,例如我们要计算 'A' 到其他节点的最短路径;
  2. 引入两个集合(S、U),
    • S集合包含已求出的最短路径的点(以及相应的最短长度);
    • U集合包含未求出最短路径的点(以及A到该点的路径,注意 如上图所示,A->C由于没有直接相连 初始时为∞);
  3. 初始化两个集合,S集合初始时 只有当前要计算的节点,A->A = 0,U集合初始时为 A->B = 4, A->C = ∞, A->D = 2, A->E = ∞ ;
  4. 从U集合中找出路径最短的点,加入S集合,例如 A->D = 2
  5. 更新U集合路径,if ( 'D 到 B,C,E 的距离' + 'AD 距离' < 'A 到 B,C,E 的距离' ) 则更新U;
  6. 循环执行 4、5 两步骤,直至遍历结束,得到A 到其他节点的最短路径。

脑中演算:

  1. 选定A节点并初始化,如上述步骤3所示,

  2. 执行上述 4、5步骤,找出U集合中路径最短的节点D 加入S集合,并根据条件 if ( 'D 到 B,C,E 的距离' + 'AD 距离' < 'A 到 B,C,E 的距离' ) 来更新U集合,

  3. 这时候 A->B, A->C 都为3,没关系。其实这时候他俩都是最短距离,如果从算法逻辑来讲的话,会先取到B点。而这个时候 if 条件变成了 if ( 'B 到 C,E 的距离' + 'AB 距离' < 'A 到 C,E 的距离' ) ,如图所示这时候A->B距离 其实为 A->D->B

    我补充了一个图, 只要记住,只要最短路径S集合里面有的,都会走一次遍历,就能想明白:

  4. 思路就是这样,往后就是循环了

  5. 算法结束

code

City Greph

package graph;

import java.util.*;

class City {
    private Map<String, List<Node>> adj = new HashMap<>();

    void addNode(String node) {
        adj.putIfAbsent(node, new ArrayList<>());
    }

    void addEdge(String node1, String node2, int weight) {
        adj.get(node1).add(new Node(node2, weight));
        adj.get(node2).add(new Node(node1, weight));
    }

    /**
     * 使用 Dijkstra 算法求解。Dijkstra 算法是一种常用的求解最短路径的算法,它的核心思想是贪心,每次选择离源点最近的节点,并使用该节点来更新其他节点的最短距离。
     *
     * 算法步骤如下:
     *
     * 1.创建一个集合 S,初始时将源节点加入。
     * 2.创建一个距离映射 dist,用于存储源点到各节点的最短路径权重和,初始时将所有节点的距离设为无穷大,源节点距离设为 0。
     * 3.从距离源点最近的节点开始,将该节点加入集合 S,并使用该节点来更新其他节点到源点的距离。
     *      具体做法是:对该节点的每个邻居节点,计算经过该节点到达该邻居节点的距离,如果这个距离比之前记录的距离更短,就更新 dist 中存储的距离值。
     *  4.重复第3步,直到目标节点被加入集合 S 为止。此时 dist 中存储的就是源点到各节点的最短距离。
     * 5.为了获得最短路径的节点序列,我们需要从目标节点开始,不断找出前驱节点,直到回到源节点为止。
     * @param source
     * @param dest
     * @return
     */
    List<String> shortestPath(String source, String dest) {
        // 创建存储顶点到起点的距离的映射
        Map<String, Integer> dist = new HashMap<>();
        // 创建存储顶点的前一个顶点的映射,用于最终构建最短路径
        Map<String, String> prev = new HashMap<>();
        // 创建存储已访问顶点的集合
        Set<String> visited = new HashSet<>();
        // 创建优先队列,用于按照顶点到起点的距离进行排序
        PriorityQueue<Node> pq = new PriorityQueue<>((a, b) -> a.weight - b.weight);

        // 初始化距离映射,将所有顶点的距离初始化为无穷大
        for (String node : adj.keySet()) {
            dist.put(node, Integer.MAX_VALUE);
        }
        // 将起点到自身的距离初始化为0
        dist.put(source, 0);
        // 将起点加入优先队列
        pq.offer(new Node(source, 0));

        // Dijkstra算法的核心部分
        while (!pq.isEmpty()) {
            // 从优先队列中取出当前距离起点最近的顶点
            Node curr = pq.poll();
            String node = curr.node;
            int weight = curr.weight;

            // 如果当前顶点已访问过,则跳过
            if (visited.contains(node)) continue;
            // 将当前顶点标记为已访问
            visited.add(node);

            // 遍历当前顶点的邻居节点
            for (Node neighbor : adj.get(node)) {
                String nextNode = neighbor.node;
                int newWeight = weight + neighbor.weight;
                // 如果通过当前顶点到达邻居节点的路径比之前计算的距离短,则更新距离映射和前一个顶点映射
                if (newWeight < dist.get(nextNode)) {
                    dist.put(nextNode, newWeight);
                    prev.put(nextNode, node);
                    // 将邻居节点加入优先队列,以便后续继续迭代
                    pq.offer(new Node(nextNode, newWeight));
                }
            }
        }

        // 构建最短路径
        List<String> path = new ArrayList<>();
        String curr = dest;
        // 从目标节点开始,沿着前一个顶点映射逆向遍历,直到回到起点
        while (prev.containsKey(curr)) {
            path.add(0, curr);
            curr = prev.get(curr);
        }
        // 将起点加入到路径中
        path.add(0, source);
        return path;
    }

    private static class Node {
        String node;
        int weight;
        Node(String n, int w) { node = n; weight = w; }
    }
}

Main

    public static void main(String[] args) {
        City c = new City();
        c.addNode("北京");
        c.addNode("天津");
        c.addNode("济南");
        c.addNode("南京");
        c.addNode("郑州");

        c.addEdge("北京", "天津", 1);
        c.addEdge("北京", "郑州", 1);
        c.addEdge("天津", "济南", 1);
        c.addEdge("济南", "南京", 1);
        c.addEdge("南京", "郑州", 1);

        List<String> path = c.shortestPath("南京", "北京");
        System.out.println("从南京到北京的最短路径: " + path); 
    }

输出:
从南京到北京的最短路径: [南京, 郑州, 北京]

posted on 2024-01-08 16:17  Mysticbinary  阅读(72)  评论(0编辑  收藏  举报