算法上机报告3——地图路由(Map Routing)
目录
四、实验内容(含源代码)
一、实验简介
本实验要求学生能够综合运用排序、搜索、图处理和字符串处理的基础算法和数据结构,
将算法理论、算法工程和编程实践相结合开发相应的软件,解决科学、工程和应用环境下的
实际问题。使学生能充分运用并掌握算法设计与分析的方法以及算法工程技术,为从事计算
机工程和软件开发等相关工作打下坚实的基础。
二、实验目的
通过本课程的学习使学生系统掌握算法设计与分析的基本概念和基本原理,理解排序、
搜索、图处理和字符串处理的算法设计理论及性能分析方法,掌握排序、搜索、图处理和字符串处理的数据结构与算法实现技术。课程强调算法的开发及 Java 实现,理解相应算法的性能特征,评估算法在应用程序中的潜在性能。
三、实验环境
1.安装Windows操作系统的计算机
2.Java的IDE——eclipse
3.安装 Java 编程环境。 引入stdlib.jar and algs4.jar。
四、实验内容
1.问题重述
实现经典的 Dijkstra 最短路径算法,并对其进行优化。 这种算法广泛应用于地理信息系统(GIS),包括 MapQuest 和基于 GPS 的汽车导航系统。
本次实验对象是图 maps 或 graphs,其中顶点为平面上的点,这些点由权值为欧氏距离的边相连成图。 可将顶点视为城市,将边视为相连的道路。 为了在文件中表示地图,我们列出了顶点数和边数,然后列出顶点(索引后跟其 x 和 y 坐标),然后列出边(顶点对),
最后列出源点和汇点。
目标: 优化 Dijkstra 算法,使其可以处理给定图的数千条最短路径查询。 一旦你读取图(并可选地预处理),你的程序应该在亚线性时间内解决最短路径问题。 一种方法是预先计算出所有顶点对的最短路径;然而,你无法承受存储所有这些信息所需的二次空间。 你的目标是减少每次最短路径计算所涉及的工作量,而不会占用过多的空间。 建议你选择下面的一些潜在想法来实现, 或者你可以开发和实现自己的想法。
想法 1. Dijkstra 算法的朴素实现检查图中的所有 V 个顶点。 减少检查的顶点数量的一种策略是一旦发现目的地的最短路径就停止搜索。 通过这种方法,可以使每个最短路径查询的运行时间与 E' log V'成比例,其中 E'和 V'是 Dijkstra 算法检查的边和顶点数。 然而,这需要一些小心,因为只是重新初始化所有距离为∞就需要与 V 成正比的时间。由于你在不断执行查询,因而只需重新初始化在先前查询中改变的那些值来大大加速查询。
想法 2. 你可以利用问题的欧式几何来进一步减少搜索时间,这在算法书的第 21.5 节描述过。对于一般图,Dijkstra 通过将 d[w]更新为 d[v] + 从 v 到 w 的距离来松弛边 v-w。 对于地图,则将 d[w]更新为 d[v] + 从 v 到 w 的距离 + 从 w 到 d 的欧式距离 − 从 v 到 d 的欧式距离。
这种方法称之为 A*算法。这种启发式方法会有性能上的影响,但不会影响正确性。
想法 3. 使用更快的优先队列。 在提供的优先队列中有一些优化空间。 你也可以考虑使用Sedgewick 程序 20.10 中的多路堆。
测试: 美国大陆文件 usa.txt 包含 87,575 个交叉口和 121,961 条道路。 图形非常稀疏 - 平均的度为 2.8。 你的主要目标应该是快速回答这个网络上的顶点对的最短路径查询。 你的算法可能会有不同执行时间,这取决于两个顶点是否在附近或相距较远。 我们提供测试这两种情况的输入文件。 你可以假设所有的 x 和 y 坐标都是 0 到 10,000 之间的整数。
注:这个问题的实验由 Bob Sedgewick 和 Kevin Wayne 设计开发(Copyright © 2004)。更多信息可参考 http://algs4.cs.princeton.edu/。
2.问题求解
该题算法的核心是Dijkstra算法,Dijkstra算法即是加点法,即从起点出发,遍历测试松弛起点邻接的每一个点,然后将最小的边加入最小生成树。
针对具体应用,可以在本来普适的Dijkstra算法上加入修改,想法1即是针对某次查询,不必生成整张地图的最小生成树,查找到了目的地即可停止搜索;想法2非常有意思,它是基于A*算法对Dijkstra算法中路径长度改进为启发函数,即采用了启发式搜索而非原Dijkstra的盲目搜索(松弛当前顶点周围的所有顶点),这是因为在地图路由中,含有目的地这一先验信息,故可以朝着目的地进行搜索;想法3即是采用其他的数据结构实现松弛,将二路堆改为多路堆,不失为一种尝试。
地图采用usa.txt文件,查询采用usa-100long.txt。
2.1亚线性时间完成初始化
我首先用原Dijkstra算法实现了地图路由,并输出查询路径、时间以及通过可视化输出其查找路径,作为其他想法修改的对比。
对于多条查询,可以设置额外的空间,我采用了队列实现,将搜索过的区域坐上标记,下次查询的时候只需要初始化上次搜索过的区域即可。
2.2想法1
想法1非常简单和朴素,也是有效的,由于已知目的地顶点编号,故只需要在Dijkstra算法中生成最小生成树的循环中加入判定条件,若当前顶点等于目的地,则退出,查找结束。
2.3想法2
想法2采用了启发式搜索算法即A*算法,其正确性这是基于以下命题
命题1
在欧氏平面中,路径长即为欧氏距离,并且存在着三角形不等式
AB+AC>BC
命题2
如果h(B)总是小于(或者等于)从结点B走到目标结点C的代价,那么A*算法是一定可以找到最优路径的。
这里h(B)为启发函数,即当前节点B到目标节点C的估算开销(即上图中的BC),从结点B走到目标结点C的代价,是算法求出的路径长,或是经过A点即AB+AC,或是直接到C点即BC,都有
AB+AC>BC 或 BC=BC
即由命题1可以得到命题2,即在地图路由中,此时的模型为欧氏平面,可以保证采用启发式搜索算法能够得到最优解。
下面推证Dijkstra算法的A*改正的路径长度表达式
此时采用启发式搜索,引入新的度量D[v](即f)代替d[v](即g),加入当前点到终点的欧氏距离
D[v]=d[v]+distance(v,t)
(f(n)=g(n)+h(n))
f(n)为新的起始点到当前点n的路径长度,g(n)为原路径长度
这里启发函数h(n)即为
h(v)=distance(v,t)
即顶点v到终点t的欧氏距离。
则松弛边e为
If(D[w]>D[v]-distance(v,t)+e(v,w)+distance(w,t))
{
D[w]=D[v]-distance(v,t)+e(v,w)+distance(w,t);
将该边加入树;
}
这里解释下为什么D[w]更新为如上式子,由D[w]定义
D[w]=d[w]+distance(w,t) 式1
又d[w]是s->w路径长,此时边e(v,w)的另一个端点为v,松弛该边,则应测试s->v+v->w的路径长,故把d[w]写为
d[w]=d[v]+e(v,w) 式2
此时式1变为
D[w]=d[v]+e(v,w)+distance(w,t) 式3
此时求d[v],由于此时只储存了新度量D,故从D[v]表达式入手
D[v]=d[v]+distance(v,t)
得d[v]
d[v]=D[v]-distance(v,t) 式4
将式(4)代入式(3)即得改进条件
D[w]=D[v]-distance(v,t)+e(v,w)+distance(w,t); 式5
式(5)即是Dijkstra算法的A*改进,或称启发式搜索改进。
此时搜索方向大致朝着终点方向搜索,并且由于三角形不等式,易证明该算法能够得到最优路径。
此时代码的修改也非常简单,只需要修改松弛时的条件表达式以及更新路径长的表达式即可。
2.4想法3
该想法也非常朴实,即将原算法采用的二叉堆数据结构改为多路堆,从而达到改进算法
性能的目的。即改变多路堆IndexMultiwayMinPQ(V,way)中的参数way的值即可。
3.3实验结果
查询usa-100long.txt文件中的查询对,结果为
Improve0是亚线性时间初始化改进
Improve1是想法1改进
Improve2是想法2改进
Improve3是想法3改进,采用的是三路堆
由此知采用亚线性时间初始化以及A*算法对算法性能改善显著。
这是时间性能上的对比,再看看空间搜索上的改进对比,查询对0-2000
1.原算法
原算法搜索了整个地图
2.想法1
由图知,想法1仍需要搜索大部分空间
3.想法2
想法2采用了启发式搜索算法,可以明显的减小搜索空间,在想法2的基础上的想法3结果相同,只是采用了多路堆。
3.4源代码
1.原算法
import java.awt.Color;
public class Dijkstra {
private static double INFINITY = Double.MAX_VALUE;
private static double EPSILON = 0.000001;
private EuclideanGraph G;
private double[] dist;
private int[] pred;
public Dijkstra(EuclideanGraph G) {
this.G = G;
}
// return shortest path distance from s to d
public double distance(int s, int d) {
dijkstra(s, d,0);
return dist[d];
}
// print shortest path from s to d (interchange s and d to print in right order)
public void showPath(int d, int s) {
dijkstra(s, d,0);
if (pred[d] == -1) {
System.out.println(d + " is unreachable from " + s);
return;
}
for (int v = d; v != s; v = pred[v])
System.out.print(v + "-");
System.out.println(s);
}
// plot shortest path from s to d
public void drawPath(int s, int d) {
dijkstra(s, d,1);
if (pred[d] == -1) return;
Turtle.setColor(Color.red);
for (int v = d; v != s; v = pred[v])
G.point(v).drawTo(G.point(pred[v]));
Turtle.render();
}
// Dijkstra's algorithm to find shortest path from s to d
public void dijkstra(int s, int d,int draw) {
int V = G.V();
// initialize
dist = new double[V];
pred = new int[V];
//points = new Point[V];
for (int v = 0; v < V; v++) dist[v] = INFINITY;
for (int v = 0; v < V; v++) pred[v] = -1;
// priority queue
IndexPQ pq = new IndexPQ(V);
for (int v = 0; v < V; v++) pq.insert(v, dist[v]);//这里可以优化
// set distance of source
dist[s] = 0.0;
pred[s] = s;
pq.change(s, dist[s]);
// run Dijkstra's algorithm
while (!pq.isEmpty()) {
int v = pq.delMin();
//// System.out.println("process " + v + " " + dist[v]);
// points[n]=G.point(v);
//n=n+1;
// v not reachable from s so stop
if (pred[v] == -1) break;
// scan through all nodes w adjacent to v
IntIterator i = G.neighbors(v);
while (i.hasNext()) {
int w = i.next();
if(draw == 1)
{
Turtle.setColor(Color.yellow);
G.point(v).drawTo(G.point(w));
Turtle.render();
try
{
for(long j=0;j<10000;j++);
}
catch(Exception e){}