第四届58topcoder编程大赛--地图路径规划

问题及背景

小明是58的一名90后程序猿,除了日常接需求写代码以外,他还特别善于思考,尤其喜欢思考和58产品业务相关的问题。随着移动互联网的发展,小明推测58将来和地理位置相关的产品服务会越来越多,比如58房产下附近的商圈、58同镇下的拼车、58速运等等都是和地理位置相关产品。和地理位置相关有一个非常重要问题是:如何以最短的时间从A地去往B地。小明想到这个问题之后,很兴奋!特别是今天上班的时候车载导航又给他引到了大山子,堵车导致他上班又迟到1小时,因此他暗下决心要设计一个更加智能的路径规划算法,一方面58产品服务能用上,更重要的是他想解决大山子这个难题。可惜这个问题太难,小明单独解决有点难,希望58的同学们能够一起帮忙设计更加智能的算法,让他以后可以花费更少的时间在路上。

问题抽象

该问题可以抽象成:给定一个地图,找出从A点至B点的最优路径,使得从A点至B点所需的时间最少。为了简化问题,我们假定地图所在的坐标系为二维平面,两点之间的直线距离为欧氏距离。

1、程序输入

程序的输入包括地图和查询请求两部分。

地图由一系列的“边”组成,每条边表示地图上两点间的一条连通路径。

边的格式为:x1,y1 x2,y2 speed

x1,y1和x2,y2表示边的起点和终点在地图上的坐标,speed表示这条边上的行驶速度,并且双向行驶的速度都是一样的。所有点的坐标值x和y都是整数,单位为km(公里),取值范围为 0<=x<=100000,0<=y<=100000,两点间的速度speed为整数,单位为km/h(公里每小时),取值范围为10<=speed<=100。

查询请求表示针对该地图的查询,每个地图包含多个query,我们需要分别计算每个query所对应的最优路径。

每个query的格式为:x1,y1 x2,y2

x1,y1和x2,y2分别表示query的起点和终点在地图上的坐标。比赛给出的所有query,起点和终点一定是连通的,即起点和终点间至少存在一条连通路径。

2、程序输出
对于每个query,需要输出“路径”和“花费时间”两部分内容。

其中,路径为包含起点和终点在内的一系列点,包含从起点至终点经过的所有点的坐标;相邻两点间的距离除以对应的速度即为这两点间的花费时间,各个点之间花费时间之和即为总的花费时间,花费时间为浮点数,单位为小时。

程序输入和输出均通过文件的形式提供,各个语言提供的框架会负责读取输入文件,并将结果保存到输出文件,参赛者不需要关心相关的文件操作。

3、问题举例

输入文件:

  21
  2,11	37,47	10
  8,7	96,0	90
  21,61	37,47	50
  88,86	96,0	60
  88,86	93,99	10
  35,88	88,86	80
  8,7	37,47	20
  21,61	35,88	50
  5,62	21,61	70
  35,88	47,51	40
  2,11	5,62	20
  35,88	93,99	20
  2,11	8,7	10
  37,47	96,0	80
  37,47	47,51	70
  47,51	88,86	40
  93,99	96,0	90
  5,62	35,88	50
  21,61	47,51	20
  47,51	96,0	50
  2,11	21,61	60
  1
  37,47	93,99

第一行表示地图的边数n,此样例地图有21条边,接下来n行分别是边的起点、终点和速度。接下来的1行表示query的数目m,此样例只有一个query,接下来m行表示每个query的起点和终点。

输出文件:

  1
  2.043406	3	37,47	96,0	93,99
  62

第一行表示query的结果数n和输入文件的query数是一样的,不一样会判错误。接下来n行是每一个query的答案,格式分别是耗时(单位为小时),点数m,以及m个点的坐标,其中第一个点和最后一个点必须是query中的起点和终点,最后一行是运行时间,运行时间单位是us,运行时间框架运行时会给出。

下图是上面地图和query的图形化表示。图中线段表示道路,线上的数字分别是道路的长度和平均速度,道路的长度可以根据输入文件的坐标点得到,求A到B的较短时间路线。在这里,我们展示了一条最短路线,其中红线就是最终得到的最短时间路线。比赛时,选手只要给出一条时间误差5%之内的路线即可。

(pic missing)

评价标准

比赛会提供多组地图,每组地图对应若干query。比赛会依次以各个地图及其对应的query作为输入,运行参赛者提交的程序,并判断各个query的输出结果是否正确。
对于一个特定的query,输出结果正确,必须同时满足以下两个条件:

1、花费时间正确。比赛允许花费时间有5%以内的误差,即程序计算得出花费时间与最优答案相比,只要差距在5%以内,即可认为是正确。

2、路径正确。程序计算得出的路径必须是存在于地图上的一条连通路径,且这条路径对应花费时间必须等于程序输出的花费时间。

需要说明的是,由于两点之间的最短路径可能不唯一,即起点和终点之间可能存在多条路径,它们的花费时间相同。因此,比赛并没有要求路径与标准答案完全相同,只要花费时间在允许的误差范围内,并且沿着路径能够得到对应的花费时间,就可以判定为正确。

比赛排名会综合考虑运行正确的地图数量和程序运行时间两个因素。

1、比赛首先计算运行正确的地图数量,运行正确的地图越多,排名越靠前。需要注意的是,针对某个地图的所有query都运行正确,才算这个地图运行正确;只有前一个地图运行正确的情况下才会继续运行下一个地图,如果某个地图运行错误,则后续的其他的地图均不再运行。

2、在运行正确地图数量相同的情况下,会考虑程序的运行时间,运行时间越短,排名越靠前。程序运行时间由init函数运行时间和多次运行search函数的时间之和两部分组成。程序运行时间由各语言的框架计算,参赛者不需要自己统计。

其他说明

(1)支持C++、Java、PHP和NodeJS四种编程语言。

(2)线上编译环境: cpp(gcc 4.4.7)、Java(jdk1.8.0_66)、PHP(7.1.10)、nodejs(v8.7.0)

(3)组委会提供了各个语言的程序框架,实现了读取输入文件、输出计算结果和统计运行时间等功能。每个语言的框架中都定义了一个名为Searcher的类,参赛者需要实现其中的init函数和search函数。其中,init为初始化函数,框架在读取完输入文件之后调用该函数,search为查询函数,针对每个输入文件中的每个query,框架都会调用一次search函数。提交代码时,参赛者只需要提交定义了Searcher类的文件(searcher.h/Searcher.java/Searcher.php/searcher.js)即可。

(4)比赛代码中只允许一个进程和一个线程,禁止使用多进程,禁止使用多线程,禁止程序中使用网络

(5)参赛者可以下载各个语言的框架进行本地调试,调试通过后再进行提交。在各个语言的框架里都包含一个示例性的输入文件和对应的标准答案,文件名分别为input.10.txt和standard_output.10.txt,参赛者可以使用这组示例文件验证自己程序的正确性。本地运行程序需要提供两个参数,分别表示输入文件和输出文件的路径。以C++框架为例,其编译出来的可执行文件名为a.out,则运行程序的方式为a.out input.10.txt output.10.txt,程序会从input.10.txt读取输入,并将运行结果保存在output.10.txt中。

(6)组委会将对所有最终获奖者进行代码review,利用题目、数据、网站漏洞取得排名的参赛者将取消成绩。


我的代码

框架代码及我的代码

这道求地图上两个坐标之间最短路径的题目,一般想到的是数据结构课堂上老师讲的几种方法,比如 DijkstraFloyd 算法,前者基本可以认为是一种回溯算法,适合解决单源最短路径问题, 后者可以认为是动态规划算法,适合解决两点最短路径问题,详情可以看其他人的博客或者翻书或者wiki之,本文不介绍。本篇要说的是,实际应用中,往往会使用另一种快速搜索算法--启发式搜索。

启发式搜索,最典型的就是 A*算法,其主要思想是:从起始点向终止点探索的过程中,优先选择代价(代价=已探索过路径的实际代价 和 剩余路径的预估代价)最小的路径点。这和 Dijkstra 算法的区别在于优先选择路径点的策略。

具体实现:

  1. 地图表示(点和边的标识方法,边一般用邻接表表示)
  2. 距离定义(一般用欧氏距离,本题里用的是耗时)
  3. 代价定义(实际代价 + 预估代价,实际代价如何初始化和更新,预估代价是否一成不变)
  4. 路径标记(探索过程中标识哪些点走过,哪些探索过,哪些点未探索过)
  5. 路径选择(如何优先选择代价最小的点,同时能眼观六路,此路不通时能及时调整到其他路径--优先队列)

差不多把以上几个问题想清楚了,编码写下来就差不多解决了这道题,同时对 A* 算法有大致了解了。

最新英雄榜

截止到2017年12月10日 18:46

致谢

感谢杨大师@yangyi 的精彩分享,我的代码借鉴了杨大师的 预估速度,更重要的是杨大师追求极致的精神是我等 IT 人士的典范。

posted @ 2017-12-10 11:49  longwind09  阅读(1149)  评论(0编辑  收藏  举报