地铁图快速寻路算法

开发 开发工具 算法
这两天,博客园里有人谈论到地铁图的实现,而之前我也和NeoRAGEx2002同学做了一个Android地铁图应用,因此,对于地铁图的寻路算法,我觉得有必要专门写一篇博客来给出我们的解决方案,供大家参考。本文所述算法的时间复杂度为O(|E|log|E|),其中|E|为边的数量。

1.概述

 

这两天,博客园里有人谈论到地铁图的实现,而之前我也和NeoRAGEx2002同学做了一个Android地铁图应用,因此,对于地铁图的寻路算法,我觉得有必要专门写一篇博客来给出我们的解决方案,供大家参考。本文所述算法的时间复杂度为O(|E|log|E|),其中|E|为边的数量。

 

 

2.概念

 

1)点和边

 

基础元素为点(地铁站)和边(两个相邻站之间的有向轨道)。

例如,经过莘庄站有1号线和5号线,含有莘庄站的边有4条,经过世纪大道站有4条线路,含有世纪大道站的边有8条。

 

2)运营段

 

在边的基础上,还有运营段的概念,即一组连续边的集合。

例如,1号线有莘庄-富锦路(发车间隔8分)、莘庄-上海火车站(发车间隔6分)、上海南站-富锦路(发车间隔8分)、上海南站-上海火车站(发车间隔6分)、富锦路-莘庄(发车间隔8分)、上海火车站-莘庄(发车间隔6分)等运营段。

 

3)代价

 

寻路算法的依据可以为时间、换乘次数、经过边数等任意非负代价,这里着重对时间进行建模。

每条边有一个乘坐时间代价,表示乘坐地铁经过该边所需要花费的时间。

每个运营段有一个等车时间代价,为通过该运营段中的边乘车需要等车的时间,通常可以假设为发车间隔时间(等车时间的最大值)或者发车间隔时间的一半(等车时间的数学期望)。

在每个点有一个换乘时间代价矩阵,表示在任意两条边之间换乘所需要花费的时间。两边之间的关系有直接连通、换乘、不连通三种。连通的换乘时间代价为0,换乘的换乘时间代价为换乘行走时间+等车时间,不连通的换乘时间代价为+∞。这个矩阵可以用稀疏矩阵表示,不连通的两边不出现。由于地铁的设计使得我们不需要考虑沿着某条线路折返的路线,我们可以将一边和它的相反边看做不连通而不是换乘,这样可以降低图的复杂度。

 

 

3.算法

 

1)思路

 

传统的最短路径算法很多,比如

Dijkstra算法,不过这种算法没有办法解决换乘时间代价问题。

广度优先算法,在加权图的时候无法得到最优解。

受限的深度优先算法,能得到结果,但路径比较长时算法时间过长。

 

我们可以考虑这样一个自然现象,雪水在山峰上融化,然后流经各个山谷。各站点就是山谷中的点,换乘站点就是山谷分成多股的交叉点。

假设起始点是山峰,水沿着各边扩散,经过一边的用时和边上的乘坐时间代价一样,从一边到一邻边,需要等待换乘时间代价。不停往起始点倒水,水不停流动,当水到达终止点时,水流经过的路径就是我们所需要的最短路径。

 

这个模型的问题在于水可以有多股水流同时流动,但是我们的算法应该有一个顺序,我们可以假设有一个水流切线,表示所有水流的最前端位置。任意边e,当其起点被水流所覆盖,而终点没有被水流覆盖时,将e加入按代价排序的切线边列表C(红黑树或平衡树实现),并记录e->水流经过的上一边。继续让水流动,则C中的第一个边e的终点最先被水流所覆盖,从C中移除e。当到达寻路的终止点时,我们可以通过从最后一条边开始回溯上一边,再上一边的上一边,直到寻路的起点,这样就获得了所需要的路径。

 

算法也可以不在终点结束,而直到水流覆盖地图上的所有点,对性能并没有明显的影响。

 

2)例子

 

如图1所示:

        图1(a) 时间代价                                图1(b) 搜索顺序

 

为了简化问题,我们假设2号线(绿色)和9号线(水色)不存在,只考虑4号线(深蓝色)和6号线(紫红色)。

图1(a)中表示了4号线和6号线的边的时间代价,其中白色表示等车时间,黄色表示乘车时间。

我们假设每个换乘站,换乘时的行走时间为4分钟。

图1(b)表示了搜索顺序,对于相同的代价,其搜索顺序不定,由切线边列表C的实现决定。

例子中的起始点为世纪大道,终止点为上海儿童医学中心。

 

切线边列表C的变化如下

  1. {1, 2, 3, 5}  
  2. {2, 3, 4, 5}  
  3. {3, 4, 5, 6}  
  4. {4, 5, 6, 9}  
  5. {5, 6, 7, 9}  
  6. {6, 7, 8, 9}  
  7. {7, 8, 9, 10, .., ..}  
  8. {8, 9, 10, .., .., ..}  
  9. {9, 10, .., .., .., ..}  
  10. {10, .., .., .., .., ..} 

需要注意到消去6的时候,增加了10、(蓝村路, 塘桥)、9的反向边三条边,消去9的时候,增加了6的反向边。

消去9时,会再次搜索到10,此时的时间代价为13+4+8=25,但因为10已经记录了其上一边,所以不再加入C。

#p#

 

3)实现

 

伪代码如下:

  1. record Vertex //点  
  2.     InEdges:List<Edge> //进站边  
  3.     OutEdges:List<Edge> //出站边  
  4.     Connection:Map<Tuple<Edge, Edge>, EdgeConnection> //边连接矩阵,包含换乘行走时间代价,当不连接时不存在  
  5.  
  6. record Edge //边  
  7.     Start:Vertex //起点  
  8.     End:Vertex //终点  
  9.     Cost:Int //乘坐时间代价  
  10.     Ranges:List<Range> //运营段  
  11.  
  12. record Range //运营段  
  13.     Edges:List<Edge> //边  
  14.     Cost:Int //等车时间  
  15.  
  16. taggedunion EdgeConnection  
  17.     Connected:Unit //直接连接  
  18.     Transferable:Int //换乘,行走时间代价  
  19.  
  20. CalculateRoute(Start:Vertex, End:Vertex):List<Edge>  
  21.     if Start == End  
  22.         return new List<Edge>() //起始点和终止点重合  
  23.  
  24.     let Previous <- new Map<Edge, Edge>() //边到上一边的映射  
  25.     let cmp <- (Comparer<Edge>)(...) //路径代价比较函数,将在下面给出  
  26.     let CutEdges <- new RedBlackTree<Edge>(cmp) //水流切线边列表  
  27.  
  28.     foreach o in Start.OutEdges  
  29.         CutEdges <- CutEdges + o  
  30.         Previous <- Previous + (o, null)  
  31.  
  32.     let e <- (Edge)(null) //终边  
  33.  
  34.     while CutEdges.Count > 0  
  35.         let i <- CutEdges.First  
  36.         CutEdges <- CutEdges - i  
  37.  
  38.         let s <- i.End  
  39.         if s == End  
  40.             e <- i  
  41.             break 
  42.  
  43.         foreach o in s.OutEdges  
  44.             if !s.Connection.ContainsKey((i, o))  
  45.                 continue 
  46.  
  47.             if Previous.ContainsKey(o)  
  48.                 continue 
  49.  
  50.             Previous <- Previous + (o, i)  
  51.             CutEdges <- CutEdges + o  
  52.  
  53.     if e == null  
  54.         return null //没有路径  
  55.  
  56.     let l <- new List<Edge>()  
  57.     while e != null  
  58.         l <- l + e  
  59.         e <- Previous(e)  
  60.  
  61.     return l.Reverse() 

下面为当寻路依据为时间时的比较函数

  1. let Time <- new Map<Edge, Int>()  
  2. let Range <- new Map<Edge, Range>()  
  3. let GetBestRange <- l:List<Range> => l.OrderBy(r => r.Cost).First  
  4. let GetTime <-  
  5.     e =>  
  6.         if e == null  
  7.             return 0  
  8.         if Time.ContainsKey(e)  
  9.             return Time(e)  
  10.         let p <- Previous(e)  
  11.         let v <- GetTime(p)  
  12.         if p != null  
  13.             let c <- e.Start.Connection((p, e))  
  14.             if c  
  15.             | Connected ->  
  16.                 let rgOld <- Range(p)  
  17.                 let rg <- GetBestRange(p.Ranges.Intersect(e.Ranges))  
  18.                 Range <- Range + (e, rg)  
  19.                 if rgOld != rg  
  20.                     v <- v - rgOld.Cost + rg.Cost  
  21.             | Transferable t ->  
  22.                 let rg <- GetBestRange(e.Ranges)  
  23.                 Range <- Range + (e, rg)  
  24.                 v <- v + rg.Cost + t  
  25.         else 
  26.             let rg <- GetBestRange(e.Ranges)  
  27.             Range <- Range + (e, rg)  
  28.             v <- v + rg.Cost  
  29.         v <- v + e.Cost  
  30.         Time <- Time + (e, v)  
  31.         return v  
  32. let cmp <-  
  33.     (l:Edge, r:Edge) =>  
  34.         return GetTime(l) - GetTime(r) 

下面为当寻路依据为换乘次数时的比较函数

  1. let TransferCount <- new Map<Edge, Int>()  
  2. let GetTransferCount <-  
  3.     e =>  
  4.         if e == null  
  5.             return 0  
  6.         if TransferCount.ContainsKey(e)  
  7.             return TransferCount(e)  
  8.         let p <- Previous(e)  
  9.         let v <- GetTransferCount(p)  
  10.         if p != null  
  11.             let c <- e.Start.Connection((p, e))  
  12.             if c  
  13.             | Connected ->  
  14.                 ()  
  15.             | Transferable _ ->  
  16.                 v += 1  
  17.         TransferCount <- TransferCount + (e, v)  
  18.         return v  
  19. let cmp <-  
  20.     (l:Edge, r:Edge) =>  
  21.         return GetTransferCount(l) - GetTransferCount(r) 

下面为当寻路依据为经过边数时的比较函数
 

  1. let StopCount <- new Map<Edge, Int>()  
  2. let GetStopCount <-  
  3.     e =>  
  4.         if e == null  
  5.             return 0  
  6.         if StopCount.ContainsKey(e)  
  7.             return StopCount(e)  
  8.         let p <- Previous(e)  
  9.         let v <- GetStopCount(p) + 1  
  10.         StopCount <- StopCount + (e, v)  
  11.         return v  
  12. let cmp <-  
  13.     (l:Edge, r:Edge) =>  
  14.         return GetStopCount(l) - GetStopCount(r) 

4.算法复杂度

认为点的入站边和出站边很少,覆盖每条边的运营段很少,并注意到GetTime运行时递归的部分总会在Time变量中缓存,可知时间比较函数的复杂度为O(1)。

CutEdges的红黑树插入删除的复杂度为O(log|E|)。

所有边最多进出CutEdges一次,可知整个算法的复杂度为O(|E|log|E|)。

 

5.结果

本文所述算法能够在O(|E|log|E|)时间内快速得到全局最佳路径。

在1GHz的单CPU手机上实测得到的上海地铁(11条线路214站)任意两站点之间的寻路时间均为200ms以下。

 

最后还是介绍下我们的应用。

矢量地铁(上海版)

支持双指无极缩放、动态寻径效果、本地地图显示。虽然我们只是一个小团队,但我们只做最好的地铁图!如果大家有啥问题和建议,欢迎给我们留言!

原文链接:http://www.cnblogs.com/Rex/archive/2012/08/12/2634401.html

责任编辑:林师授 来源: 博客园
相关推荐

2017-07-26 15:59:51

寻路算法Dijkstra游戏

2023-12-20 08:35:54

Dijkstra算法A*算法计算机图形学

2021-01-28 10:55:31

算法可视化数据

2009-06-10 13:25:46

RFID发展无线网络

2012-05-18 13:59:45

HTML5

2024-10-08 15:16:23

SQL地铁换乘数据库

2022-12-14 17:42:48

军棋工兵算法

2013-10-14 13:52:06

Windows 8微软

2016-01-27 14:47:02

云监控华为

2009-07-29 11:16:08

浪潮四路服务器

2023-08-24 22:13:31

2023-01-15 17:57:01

2014-10-30 15:14:54

快速排序编程算法

2019-04-28 12:00:56

地铁数据代码

2020-09-17 17:46:20

Python地铁线路图

2010-04-29 13:04:39

Google数据中心

2014-12-17 09:16:33

漏洞北京地铁系统

2016-02-01 18:19:49

数据中心建设华为

2017-12-07 08:03:54

华为

2021-03-04 07:24:28

排序算法优化
点赞
收藏

51CTO技术栈公众号