算法笔记-最短路径规划
Posted java程序员笔记
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法笔记-最短路径规划相关的知识,希望对你有一定的参考价值。
关于图这种数据结构总结的时候,总结了深度优先搜索和广度优先搜索,这两种算法主要是针对无权图的搜索算法。
今天总结针对有权图的搜索算法,最短路径。
常用的地图导航软件,规划行程的时候,会给出最短路径,最少用时,最少红绿灯等不同路径,这种的问题都可以最短路径算法解决。
最短路径
实现算法之初,先确定数据结构。针对地图规划,我们用有权图来表示。地图中的分叉路口映射为图中的顶点,路口与下一个路口之间的距离,映射为图上两个顶点之间边的权重。如果是单行道,画一条有向的边,如果是双行道,画两条有向的边。数据结构代码实现如下:
public class Graph {
// 邻接表
private LinkedList<Edge> adj[];
// 顶点个数
private int v;
// 初始化有向有权图
public Graph(int v) {
this.v = v;
this.adj = new LinkedList[v];
for (int i = 0; i < v; ++i) {
this.adj[i] = new LinkedList<>();
}
}
// 添加一条边
public void addEdge(int s, int t, int w) {
this.adj[s].add(new Edge(s, t, w));
}
// 边
private class Edge {
// 边的起始顶点编号
public int sid;
// 边的终止顶点编号
public int tid;
// 权重
public int w;
// 初始化边
public Edge(int sid, int tid, int w) {
this.sid = sid;
this.tid = tid;
this.w = w;
}
}
// 下面这个类是为了 dijkstra 中小顶堆对比条件 实现用的
private class Vertex implements Comparable<Vertex> {
public int id; // 顶点编号 ID
public int dist; // 从起始顶点到这个顶点的距离
public Vertex(int id, int dist) {
this.id = id;
this.dist = dist;
}
@Override
public int compareTo(Vertex o) { // 按照 dist 从小到大排序
if (o.dist > this.dist)
return -1;
else
return 1;
}
}
}
Vertex 类主要是在小顶堆中用来做排序的依据。
单源最短路径
Dijkstra 算法。代码实现如下:
// 从顶点 s 到顶点 t 的最短路径
public void dijkstra(int s, int t) {
// 用来还原最短路径
int[] predecessor = new int[this.v];
// 记录起始顶点到这个顶点的距离
Vertex[] vertexes = new Vertex[this.v];
// 初始化 dist 为无穷大
for (int i = 0; i < v; ++i) {
vertexes[i] = new Vertex(i, Integer.MAX_VALUE);
}
// 小顶堆
PriorityQueue<Vertex> queue = new PriorityQueue<>();
// 标记是否进入过队列
boolean[] inQueue = new boolean[this.v];
// 先把起始顶点放到队列中
queue.add(vertexes[s]);
//自己到自己的距离为 0
vertexes[s].dist = 0;
//标记已经进入队列
inQueue[s] = true;
while (!queue.isEmpty()) {
// 小顶堆,取出 dist 最小的顶点
Vertex minVertex = queue.poll();
if (minVertex.id == t){
break; // 最短路径产生了
}
//遍历该顶点指向其他定点的边
for (int i = 0; i < adj[minVertex.id].size(); ++i) {
// 取出一条 minVetex 相连的边
Edge e = adj[minVertex.id].get(i);
// 取出顶点 minVertex 指向的下一个顶点 nextVertex
Vertex nextVertex = vertexes[e.tid];
// 找到一条到 nextVertex 更短的路径
if (minVertex.dist + e.w < nextVertex.dist) {
// 更新 dist
nextVertex.dist = minVertex.dist + e.w;
// 更新前驱顶点 nextVertex 的前驱顶点是 nextVertex
predecessor[nextVertex.id] = minVertex.id;
// 如果没有在队列中
if (inQueue[nextVertex.id] == false) {
// 就把它放到队列中
queue.add(nextVertex);
// 标记访问
inQueue[nextVertex.id] = true;
}
}
}
}
// 输出最短路径
System.out.print(s);
print(s, t, predecessor);
}
// 输出最短路径
private void print(int s, int t, int[] predecessor) {
if (s == t)
return;
print(s, predecessor[t], predecessor);
System.out.print("->" + t);
}
上述代码有详细的注释,但是过程有些复杂,下面在梳理一遍过程。
确定起始点,然后遍历起始点指出去的所有边,边的终点编号 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,有向图改为无向图,利用广度优先搜索,两点间的距离就是最少红绿灯路径规划。
分段规划最短路径:如果规划北京到上海的最短路径,就需要重新换一下策略。首先,地图缩小,构建北京到上海关键路口的有权有向图,规划出最短路径。然后,再对每一段路径,进行最短路径规划。
缩小范围规划最短路径:无需遍历图中所有顶点,可划定包含岂止顶点的小范围图,然后再规划最短路径。
总结
本文创作灵感来源于 极客时间 王争老师的《数据结构与算法之美》课程,通过课后反思以及借鉴各位学友的发言总结,现整理出自己的知识架构,以便日后温故知新,查漏补缺。
初入算法学习,必是步履蹒跚,一路磕磕绊绊跌跌撞撞。看不懂别慌,也别忙着总结,先读五遍文章先,无他,唯手熟尔~ 与诸君共勉
以上是关于算法笔记-最短路径规划的主要内容,如果未能解决你的问题,请参考以下文章
算法动态规划 ⑥ ( 骑士的最短路径 II | 问题分析 | 代码示例 )