地铁线路查询算法

  有天晚上还没睡着的时候,突然想起以前做课程设计时,有同学搞那个公交线路查询,老师上课时还提过什么只能查出换乘两次的线路,我不知道是那程序限制了换乘的次数还是那个算法查不出换乘两次以上的线路了,如果是后者,那个算法就有点糟糕。后来就想,如果给我做的话怎么做呢,别人写公交查询,我这个列车迷就写个地铁线路查询,其实感觉地铁的比公交的简单多了。

  这样的线路查询,说白了其实也是图的遍历问题,大二学数据结构的时候,在课上老师有说到图的遍历算法能解决线路查询问题,也说到某些物体移动的动画,图能搞出来。后者我完全不明白了。前者我现在还能用得上。

  最直接的方法,就把各个站点作为一个结点连在一起,成为一个图,像这样(估计有不少园友对这幅图很熟悉)

相邻的两个站点互为可达,线路查找时就通过图的深度遍历或广度遍历查找出出发点和目的地的线路。这种算法直接明了,简单易写,可是效率不高。我也写了一个,用来作为参考。

  我调整后的算法思路是这样的,先不理这个站点的下一个或上一个站点是什么,我只管这个站点在哪条线路上,把起点和终点的线路找出来,用线路作为图的结点,能换乘的两条线路互为可达,像这样(这幅图比上一幅丑多了,莫笑)

同样也是通过图的深度或广度遍历查找出线路,由于地铁线路肯定比地铁站要少很多,对线路图的遍历会比站点图的遍历要快。而本算法采用的是图的广度遍历算法,其实应该最好用层次遍历的,但是没用上,估计速度会更快。

  好了,放了文字又放图,怎么能少得了代码,既然是算法,肯定要有数据结构,先把实体类列出来

1     public class StationEnt
2     {
3         public string StationName { get; set; }
4         public Dictionary<LineEnt,int> PlaceLine { get; set; }
5     }

  首先是站点的,里面有两个成员,一个是站点名,另一个是所在线路,一个站当然可以位于多条线路里面。因为这里没有上一站,下一站这样的结构,所以在线路后面加了一个编号来确定站点所在的位置,同时一个站点没理由重复位于同一条线路的,所以这里用了个dictionary的泛型。

1     public class LineEnt
2     {
3         public string LineName { get; set; }
4         public List<StationEnt> Stations { get; set; }
5         public List<Tuple<LineEnt,StationEnt>> TranformStations { get; set; }
6         public bool IsRoundLine { get; set; }
7     }

  有站点当然要有线路,线路的成员有四个,一个是线路名,线路包括站点的集合,线路的换乘站,还有最后一个属性是标识这条线路是否环线,因为环线会有另一种的处理方式。换乘站集合用了一个二元组的List存储,考虑到两条线路的换乘站有可能不止一个,而换乘站又要知道是换乘哪条线路的。

1     public class Station2Station
2     {
3         public StationEnt FromStation { get; set; }
4         public LineEnt Line { get; set; }
5         public StationEnt ToStation { get; set; }
6     }

  还有一个,用于线路查询时的,方便记录路线,这只是整条线路中的一段,看字面意思都会明白,起点站和到达站,还有通过的线路。

  整个算法封装在一个类中:MetroNetModel。为了减少堆内存的使用量,这里都用了引用类型,对于某个站点、某条线路这些实例,整个类里面只有一个。

下面是类的私有字段

1         /// <summary>
2         /// 全线网站点集合
3         /// </summary>
4         protected Dictionary<string, StationEnt> _stationCollection;
5         /// <summary>
6         /// 全线网线路集合
7         /// </summary>
8         protected List<LineEnt> _lineCollection;

这两个是对于一个地铁线路网是必有的

 1         /// <summary>
 2         /// 最短线路的站点数
 3         /// </summary>
 4         protected int _minLine;
 5
 6         /// <summary>
 7         /// 最短换乘次数
 8         /// </summary>
 9         protected int _minTransCount;
10
11         /// <summary>
12         /// 最短的线路段集合
13         /// </summary>
14         protected List<List<Station2Station>> _shortestLines;
15         /// <summary>
16         /// 最短的线路集合
17         /// </summary>
18         protected List<List<StationEnt>> _shortestWays;

这几个是查询中要用到的字段

  类的构造函数如下,初始化各个集合,最后调用的FieltLines()方法是给站点集合和线路集合填充对象的,算是读取数据的方法吧!

1         public MetroNetModel2()
2         {
3             _minLine = _minTransCount=int.MaxValue;
4             _shortestLines = new List<List<Station2Station>>();
5             _shortestWays = new List<List<StationEnt>>();
6             _lineCollection = new List<LineEnt>();
7             _stationCollection = new Dictionary<string, StationEnt>();
8             FieltLines();
9         }

  整个类就只有一个公共的方法GuedeMetroWay2(string fromStation, string toStation),输入的是起点站和目标站的名称。方法体如下

 1         public string GuedeMetroWay2(string fromStation, string toStation)
 2         {
 3             //验证站点存在
 4             if (!_stationCollection.ContainsKey(fromStation))
 5                 return fromStation + " is not contain";
 6             if (!_stationCollection.ContainsKey(toStation))
 7                 return toStation + " is not contain";
 8             if (fromStation == toStation) return fromStation;
 9
10             StationEnt start = _stationCollection[fromStation];
11             StationEnt end = _stationCollection[toStation];
12             List<Station2Station> stationList;
13             List<LineEnt> lineHis;
14
15             //重调两个最值
16             _minLine = _minTransCount = int.MaxValue;
17
18             //遍历这个起点站所在的线路,然后分别从这些线路出发去寻找目的站点
19             foreach (KeyValuePair<LineEnt,int> line in start.PlaceLine)
20             {
21                 stationList = new List<Station2Station>();
22                 lineHis = new List<LineEnt>() { line.Key };
23                 GuideWay2(0, start, line.Key, end, stationList, lineHis);
24             }
25             //去除站点较多的线路
26             ClearLongerWays();
27             //生成线路的字符串
28             string result = ConvertStationList2String();
29
30             //清空整个查找过程中线路数据
31             _shortestLines.Clear();
32             _shortestWays.Clear();
33
34             return result;
35         }

  由于不知道各个站间的时间间隔,算法中只能按站点的数量来判定哪条线路更快,这样可能就是与百度上查找的结果有出入的原因吧!

  上面查找的核心方法是GuideWay2,它是一个图的广度遍历的递归算法,传入的参数分别是当前换乘次数,当前站,当前线路,目标站,途径线路段的集合,已经到过的线路,方法定义如下

 1         protected void GuideWay2(int transLv, StationEnt curStation, LineEnt curLine,
 2                                 StationEnt endStation, List<Station2Station> stationList,
 3                                 List<LineEnt> lineHis)
 4         {
 5             //如果当前换乘的次数比换乘次数最小值
 6             //就不用再找了,找出来的线路肯定更长
 7             if (transLv > _minTransCount) return;
 8             //判定是否已经到达目标站的线路了,若是表明一直查找成功了
 9             if (IsSameLine2(curStation, endStation,curLine))
10             {
11                 Station2Station s2s = new Station2Station()
12                     { FromStation = curStation, Line = curLine, ToStation = endStation };
13                 stationList.Add(s2s);
14                 //若当前换乘次数比记录值要小,清空之前的线路段
15                 if (_minTransCount > transLv)
16                     _shortestLines.Clear();
17
18                 _shortestLines.Add(stationList.ToArray().ToList());
19                 stationList.Remove(s2s);
20                 _minTransCount = transLv;
21                 return;
22             }
23             List<Tuple<LineEnt, StationEnt>> transform = curLine.TranformStations;
24             //遍历一下当前线路的换乘站,从而递归找出到目标站的线路
25             foreach (Tuple<LineEnt, StationEnt> item in transform)
26             {
27                 //如果这条线路已经到过的,进入下次循环
28                 if (lineHis.Contains(item.Item1)) continue;
29
30
31
32                 lineHis.Add(item.Item1);
33                 Station2Station s2s = new Station2Station()
34                     { FromStation = curStation, Line = curLine, ToStation = item.Item2 };
35                 stationList.Add(s2s);
36                 //递归调用
37                 GuideWay2(transLv + 1, item.Item2, item.Item1, endStation, stationList, lineHis);
38                 //清除集合里的值,以这种方式减少内存使用量,提高效率
39                 lineHis.Remove(item.Item1);
40                 stationList.Remove(s2s);
41             }
42         }

下面是其他辅助的方法,不作一一介绍了

  1         /// <summary>
  2         /// 清除站点较多的线路
  3         /// </summary>
  4         protected void ClearLongerWays()
  5         {
  6             _shortestWays.Clear();
  7             int curCount = 0;
  8             List<StationEnt> way = null;
  9             List<StationEnt> temp=null;
 10             foreach (List<Station2Station> innerList in _shortestLines)
 11             {
 12                 curCount = 0;
 13                 way = new List<StationEnt>();
 14                 foreach (Station2Station item in innerList)
 15                 {
 16                     temp = GetWayStations(item.FromStation, item.ToStation, item.Line);
 17                     curCount += temp.Count;
 18                     if (curCount > _minLine) break;
 19                     way.AddRange(temp);
 20                 }
 21                 if (curCount == _minLine)
 22                     _shortestWays.Add(way);
 23                 else if (curCount < _minLine)
 24                 {
 25                     _shortestWays.Clear();
 26                     _shortestWays.Add(way);
 27                     _minLine = curCount;
 28                 }
 29             }
 30         }
 31
 32         /// <summary>
 33         /// 把线路段转换成字符串
 34         /// </summary>
 35         /// <returns></returns>
 36         protected string ConvertStationList2String()
 37         {
 38             string result = string.Empty;
 39             foreach (List<StationEnt> innerList in _shortestWays)
 40             {
 41                 foreach (StationEnt item in innerList)
 42                 {
 43                     result += item.StationName + " ==> ";
 44                 }
 45                 result += " \r\n\r\n ";
 46             }
 47             result = result.Trim(‘\n‘).Trim(‘\r‘).Trim(‘\n‘).Trim(‘\r‘);
 48             return result;
 49         }
 50
 51         /// <summary>
 52         /// 判定两个站是否存在给定线路中
 53         /// </summary>
 54         /// <param name="station1"></param>
 55         /// <param name="station2"></param>
 56         /// <param name="line"></param>
 57         /// <returns></returns>
 58         protected bool IsSameLine2(StationEnt station1, StationEnt station2, LineEnt line)
 59         {
 60             bool result = line.Stations.Contains(station1) && line.Stations.Contains(station2);
 61             return result;
 62         }
 63
 64         /// <summary>
 65         /// 获取站点1和站点2在给定线路上最短的途径站点集合
 66         /// </summary>
 67         /// <param name="station1"></param>
 68         /// <param name="station2"></param>
 69         /// <param name="line"></param>
 70         /// <param name="flag"></param>
 71         /// <returns></returns>
 72         protected List<StationEnt> GetWayStations(StationEnt station1, StationEnt station2, LineEnt line, bool flag = true)
 73         {
 74             List<StationEnt> result = new List<StationEnt>();
 75             int sIndex, eIndex;
 76             //对于环线作的处理
 77             if (line.IsRoundLine && flag)
 78             {
 79                 int stationCount = line.Stations.Count + 1;
 80                 int forwardCount = station1.PlaceLine[line] - station2.PlaceLine[line];
 81                 int opposite = stationCount - forwardCount;
 82
 83                 if (Math.Abs(forwardCount) > Math.Abs(opposite))
 84                 {
 85                     result.AddRange(GetWayStations(station1, line.Stations.First(), line, false));
 86                     result.AddRange(GetWayStations(line.Stations.First(), station2, line, false));
 87                     return result;
 88                 }
 89             }
 90             sIndex = station1.PlaceLine[line];
 91             eIndex = station2.PlaceLine[line];
 92             List<StationEnt> stations = line.Stations;
 93             if (station1.PlaceLine[line] <= station2.PlaceLine[line])
 94             {
 95                 for (int i = sIndex; i <= eIndex; i++)
 96                     result.Add(stations[i]);
 97             }
 98             else
 99             {
100                 for (int i = sIndex; i >= eIndex; i--)
101                     result.Add(stations[i]);
102             }
103             return result;
104         }

Main方法里的测试代码

 1             Model.MetroNetModel2 metro = new Model.MetroNetModel2();
 2             DateTime s = DateTime.Now;
 3
 4             Console.WriteLine(metro.GuedeMetroWay2("祖庙", "鹭江"));
 5             Console.WriteLine(metro.GuedeMetroWay2("祖庙", "三元里"));
 6             Console.WriteLine(metro.GuedeMetroWay2("祖庙", "沙园"));
 7             Console.WriteLine(metro.GuedeMetroWay2("祖庙", "五山"));
 8             Console.WriteLine(metro.GuedeMetroWay2("鹭江", "祖庙"));
 9             Console.WriteLine(metro.GuedeMetroWay2("大学城北", "祖庙"));
10             Console.WriteLine(metro.GuedeMetroWay2("祖庙", "嘉禾望岗"));
11             Console.WriteLine(metro.GuedeMetroWay2("烈士陵园", "中大"));
12             Console.WriteLine(metro.GuedeMetroWay2("林和西", "体育中心"));
13             Console.WriteLine(metro.GuedeMetroWay2("林和西", "海心沙"));
14             Console.WriteLine(metro.GuedeMetroWay2("体育中心", "海心沙"));
15             Console.WriteLine(metro.GuedeMetroWay2("体育中心", "天河南"));
16             DateTime e = DateTime.Now;
17             Console.WriteLine(e - s);
18             Console.ReadLine();

运行结果图

  由于不懂得如何衡量一个算法的优劣,只会通过起止时间的间隔来判断,此外我还写了个最原始的遍历站点的算法来作参照,执行同样的线路查询,心里有些忐忑,上面第一幅图是我改的算法,第二幅是遍历站点的算法。因为发现差别不明显,时间上是差不多的,偶尔站点图的遍历还会比线路图的遍历要快(都是这堆查询)。直到让某个查询重复执行10000次,看了结果,我才松了口气

            for (int i = 0; i < 10000; i++)
            {
                metro.GuedeMetroWay2("沙园", "祖庙");
            }

  上面的时间是线路图遍历的,下面那个是站点图遍历的,而且这个结果很稳定,都是线路图完胜的。

  当初写这个算法时与某个饭说过,我要写佛*市的地铁查询线路,她不屑一顾,也许是水平太低了吧,其实我是写的是通用地铁的线路查询,没局限在一个城市,只要地铁线网的数据正确就行了。第一次写算法的博文,太浅显的内容了,主要是对算法的研究不深入,写过的算法不多。这些本是在校时同学们写的东西,在这个时候我却拿来自娱自乐。当初学数据结构的时候没学好,觉得辜负教我们数据结构的张老师,在大三大四几次重要场合碰过面,最后一次是答辩时,我表现极差,大糗了一场。能有什么改进的,还请各位园友指出。谢谢!

时间: 2024-10-12 08:35:01

地铁线路查询算法的相关文章

石家庄地铁线路查询系统

石家庄地铁线路查询系统开发: 合作人:李玉超 数据库的设计为:建立了一张表,有line_id(路线号).stop_id(站号).stop_name(站名).change(某站可换乘的线号)这几列. stop_id所显示的序号首位也可代表line_id,后两位为该站在其所在线路上的一个顺序排序序号,可以体现其位置. 设计思想: 将所有的站点可分为两类:一种是只在一条线路上(普通点),一种是可在两条线路上,即为两条线路交点(换乘点). 所以可以分为3种情况: ①:起始点:普通点   终点:普通点 ②

最短路径(图论-北京地铁线路查询)

目录 1 int main(){ 2 3 printf("北京地铁乘坐线路查询系统\n"); 4 /*---------------------------------------------------*/ 5 6 /*---------------------------------------------------*/ 7 freopen("bgstations.txt","r",stdin); 8 int LINE; /*地铁总线数*/

天津地铁线路最短路径计算项目规划

天津地铁线路路径查询项目规划 一.项目介绍 实现一个帮助进行地铁出行路线规划的命令行程序. 二.项目完成预估表 PSP 2.1 Personal Software Process Stages Time Time Planning 计划 · Estimate · 估计这个任务需要多少时间 1day Development 开发 · Analysis · 需求分析 (包括学习新技术) 3day · Design Spec · 生成设计文档 1day · Design Review · 设计复审 (

北京地铁乘坐线路查询

[问题描述] 编写一个程序实现北京地铁最短乘坐(站)线路查询,输入为起始站名和目的站名,输出为从起始站到目的站的最短乘坐站换乘线路.注:1. 要求采用Dijkstra算法实现:2)如果两站间存在多条最短路径,找出其中的一条就行. [输入形式] 文件bgstations.txt为数据文件(可从课程网站中课程信息处下载),包含了北京地铁的线路及车站信息.其格式如下: <地铁线路总条数> <线路1> <线路1站数> <站名1> <换乘状态> <站

地铁线路项目设计与分析

#地铁线路项目设计与分析 ##一.项目介绍 #####实现一个帮助进行地铁出行路线规划的命令行程序 ##二.项目计划表 | PSP2.1 | Personal Software Process Stages | Time | | --- | --- | --- | | Planning | 计划 | | · Estimate | · 估计这个任务需要多少时间 | 2h | Development | 开发| | · Analysis | · 需求分析 (包括学习新技术) | 6h | · Des

地铁线路项目

设计需求 1.思考并设计一个简明易懂,可灵活扩张,方便读取的文件格式在文本文件中存储地铁信息 2.实现一个支持显示地铁线路及相关信息与计算换乘的程序 3.实现查询指定地铁线路,指定地铁站点信息等基础查询操作 4.当用户输入两个站点时,显示两个站点之间的最短线路和换乘信息,并将线路信息写入文本文件记录下来 5.设计的软件对于各中各样的出错情况要尽可能进行精确报错 6.测试代码并优化 实现思路 地铁站的相关信息有线路,站点名称,是否开通,是否换乘. 线路编号 站点名称 是否开通 是否换乘 1 刘园

地铁线路问题分析

一.任务: 实现一个帮助进行地铁出行路线规划的命令行程序,能处理正确输入的命令行的计算地铁线路最短路径. 二.设计: 输入格式:选择json格式来输入,便于阅读. 需求1:显示地铁线路信息 将地铁线路信息等用一个文本文件以 subway.txt的形式保存起来,应保存的信息应包括地铁线路名称.各个地铁站点的名称以及车站换乘信息,使得应用程序可以通过读取这个文件,就能掌握关于北京地铁线路的所有信息. java subway -map subway.txt 需求2:查询指定路线经过的站点 查询指定地铁

天津地铁线路项目设计与分析

天津地铁线路项目设计与分析 项目需求分析:1.设计地铁线路的信息存储文件,如subway.txt 2.实现基础的查询操作(实现指定地铁线经过的站点的查询等) 3.实现最短路径计算,可以查询出发站与目标站的最短路径 设计思路: 1.采用java语言编程 2.地铁线路数据格式: 1号线:站点1 站点2 ... 2号线:站点1 站点2 ... 3号线:站点1 站点2 .........采用subway.txt文件格式进行存储输出等操作 3 洪湖里 西站 6号线 复兴路采用routine.txt文件格式

地铁线路设计

1.需求分析 需要完成的任务是实现一个地铁出行线路规划的命令行程序,任务要求的是北京地铁.下图是北京地铁线路图 1.首先,我们要把该线路图用一个文本文件(.txt格式)存起来,例如subway.txt.文件中要包含各条线路名称,各个站点,以及各个可换乘站点的信息,方便程序读该线路图,并进行线路规划. 2.启动程序时要让程序自动读取该线路图. 3.然后用户输入出发地站点和目的地站点,程序需要立刻计算出两站之间的最短路径并输出. 2.设计思路 本次项目我打算使用java语言进行编程,因为我个人对该语