算法笔记-最短路径规划

Posted java程序员笔记

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法笔记-最短路径规划相关的知识,希望对你有一定的参考价值。

关于图这种数据结构总结的时候,总结了深度优先搜索和广度优先搜索,这两种算法主要是针对无权图的搜索算法。

今天总结针对有权图的搜索算法,最短路径。

常用的地图导航软件,规划行程的时候,会给出最短路径,最少用时,最少红绿灯等不同路径,这种的问题都可以最短路径算法解决。

最短路径

实现算法之初,先确定数据结构。针对地图规划,我们用有权图来表示。地图中的分叉路口映射为图中的顶点,路口与下一个路口之间的距离,映射为图上两个顶点之间边的权重。如果是单行道,画一条有向的边,如果是双行道,画两条有向的边。数据结构代码实现如下:

 
   
   
 
  1. public class Graph {

  2.    // 邻接表

  3.    private LinkedList<Edge> adj[];

  4.    // 顶点个数

  5.    private int v;

  6.    // 初始化有向有权图

  7.    public Graph(int v) {

  8.        this.v = v;

  9.        this.adj = new LinkedList[v];

  10.        for (int i = 0; i < v; ++i) {

  11.            this.adj[i] = new LinkedList<>();

  12.        }

  13.    }

  14.    // 添加一条边

  15.    public void addEdge(int s, int t, int w) {

  16.        this.adj[s].add(new Edge(s, t, w));

  17.    }

  18.    // 边

  19.    private class Edge {

  20.        // 边的起始顶点编号

  21.        public int sid;

  22.        // 边的终止顶点编号

  23.        public int tid;

  24.        // 权重

  25.        public int w;

  26.        // 初始化边

  27.        public Edge(int sid, int tid, int w) {

  28.            this.sid = sid;

  29.            this.tid = tid;

  30.            this.w = w;

  31.        }

  32.    }

  33.    // 下面这个类是为了 dijkstra 中小顶堆对比条件  实现用的

  34.    private class Vertex implements Comparable<Vertex> {

  35.        public int id; // 顶点编号 ID

  36.        public int dist; // 从起始顶点到这个顶点的距离

  37.        public Vertex(int id, int dist) {

  38.            this.id = id;

  39.            this.dist = dist;

  40.        }

  41.        @Override

  42.        public int compareTo(Vertex o) { // 按照 dist 从小到大排序

  43.            if (o.dist > this.dist)

  44.                return -1;

  45.            else

  46.                return 1;

  47.        }

  48.    }

  49. }

Vertex 类主要是在小顶堆中用来做排序的依据。

单源最短路径

Dijkstra 算法。代码实现如下:

 
   
   
 
  1.    // 从顶点 s 到顶点 t 的最短路径

  2.    public void dijkstra(int s, int t) {

  3.        // 用来还原最短路径

  4.        int[] predecessor = new int[this.v];

  5.        // 记录起始顶点到这个顶点的距离

  6.        Vertex[] vertexes = new Vertex[this.v];

  7.        // 初始化 dist 为无穷大

  8.        for (int i = 0; i < v; ++i) {

  9.            vertexes[i] = new Vertex(i, Integer.MAX_VALUE);

  10.        }

  11.        // 小顶堆

  12.        PriorityQueue<Vertex> queue = new PriorityQueue<>();

  13.        // 标记是否进入过队列

  14.        boolean[] inQueue = new boolean[this.v];

  15.        // 先把起始顶点放到队列中

  16.        queue.add(vertexes[s]);

  17.        //自己到自己的距离为 0

  18.        vertexes[s].dist = 0;

  19.        //标记已经进入队列

  20.        inQueue[s] = true;

  21.        while (!queue.isEmpty()) {

  22.            // 小顶堆,取出 dist 最小的顶点

  23.            Vertex minVertex = queue.poll();

  24.            if (minVertex.id == t){

  25.                break; // 最短路径产生了

  26.            }

  27.            //遍历该顶点指向其他定点的边

  28.            for (int i = 0; i < adj[minVertex.id].size(); ++i) {

  29.                // 取出一条 minVetex 相连的边

  30.                Edge e = adj[minVertex.id].get(i);

  31.                // 取出顶点 minVertex 指向的下一个顶点 nextVertex

  32.                Vertex nextVertex = vertexes[e.tid];

  33.                // 找到一条到 nextVertex 更短的路径

  34.                if (minVertex.dist + e.w < nextVertex.dist) {

  35.                    // 更新 dist

  36.                    nextVertex.dist = minVertex.dist + e.w;

  37.                     // 更新前驱顶点 nextVertex 的前驱顶点是 nextVertex

  38.                    predecessor[nextVertex.id] = minVertex.id;

  39.                     // 如果没有在队列中

  40.                    if (inQueue[nextVertex.id] == false) {

  41.                        // 就把它放到队列中

  42.                        queue.add(nextVertex);

  43.                        // 标记访问

  44.                        inQueue[nextVertex.id] = true;

  45.                    }

  46.                }

  47.            }

  48.        }

  49.        // 输出最短路径

  50.        System.out.print(s);

  51.        print(s, t, predecessor);

  52.    }

  53.    // 输出最短路径

  54.    private void print(int s, int t, int[] predecessor) {

  55.        if (s == t)

  56.            return;

  57.        print(s, predecessor[t], predecessor);

  58.        System.out.print("->" + t);

  59.    }

上述代码有详细的注释,但是过程有些复杂,下面在梳理一遍过程。

确定起始点,然后遍历起始点指出去的所有边,边的终点编号 tid 对应数组 predecessor 的下标(predecessor[ tid ]),边的权重 w 对应 predecessor[ tid ] 的值(predecessor[ tid ] = w),数组 predecessor 存储起始点到达该顶点的距离。

通过遍历边得到的顶点依次放入小顶堆中,并做访问标记,下一次小顶堆中距离起止点最小的顶点出列。

然后,对出列的顶点,继续上面的步骤,直到再次出列的顶点的编号就是我们要找的终点。

整个算法过程如上述描述,其中细节处理需要我们特别注意。更新起始点到访问到的顶点的距离,标记已访问的顶点,记录访问顶点的前驱访问顶点。

Dijkstra 算法的时间复杂度

上述代码中,复杂逻辑就是 while 循环体。

由代码我们可以看到,while 循环最多循环 V 次,V 代表顶点个数。

while 体中 for 循环次数不确定,可以标记为 E1,E2,一直到EV。把这些所有加起来,总和不会大于 E。for 循环中涉及从优先级队列中添加元素,更新元素,时间复杂度为 O(logV)。

所以整段代码的时间复杂度是 O(E*logV)。

补充

如果是最少时间路径规划:算法不变,边的权重改为路程时间的值,就可以解决问题。

如果是最少红绿灯路径规划:边的权重变成 1,有向图改为无向图,利用广度优先搜索,两点间的距离就是最少红绿灯路径规划。

分段规划最短路径:如果规划北京到上海的最短路径,就需要重新换一下策略。首先,地图缩小,构建北京到上海关键路口的有权有向图,规划出最短路径。然后,再对每一段路径,进行最短路径规划。

缩小范围规划最短路径:无需遍历图中所有顶点,可划定包含岂止顶点的小范围图,然后再规划最短路径。

总结

本文创作灵感来源于 极客时间 王争老师的《数据结构与算法之美》课程,通过课后反思以及借鉴各位学友的发言总结,现整理出自己的知识架构,以便日后温故知新,查漏补缺。

初入算法学习,必是步履蹒跚,一路磕磕绊绊跌跌撞撞。看不懂别慌,也别忙着总结,先读五遍文章先,无他,唯手熟尔~ 与诸君共勉

以上是关于算法笔记-最短路径规划的主要内容,如果未能解决你的问题,请参考以下文章

算法动态规划之图的最短路径(C++源码)

算法动态规划 ⑥ ( 骑士的最短路径 II | 问题分析 | 代码示例 )

算法动态规划 ⑥ ( 骑士的最短路径 II | 问题分析 | 代码示例 )

航线规划必学:Dijkstra最短路径算法

图论动态规划算法——Floyd最短路径

leetcode之最短路径+记忆化dfs+bfs+动态规划刷题总结