个人项目-地铁出行线路规划程序-程序员宅基地

技术标签: c#  markdown  系统架构  


欢迎大家关注我们的软工团队:http://www.cnblogs.com/Default1406/

PSP表格

PSP 2.1 Personal Software Process Stages Planning Time(H) Used Time(H)
Planning 计划 0.5 0.25
· Estimate · 估计这个任务需要多少时间 0.5 0.25
Development 开发 25.5 45.9
· Analysis · 需求分析 (包括学习新技术) 10 13
· Design Spec · 生成设计文档 2 3
· Design Review · 设计复审 (和同事审核设计文档) 0.25 0.1
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 0.25 0.1
· Design · 具体设计 2 4
· Coding · 具体编码 8 24
· Code Review · 代码复审 1 0.2
· Test · 测试(自我测试,修改代码,提交修改) 2 1.5
Reporting 报告 2 3
· Test Report · 测试报告 1 2
· Size Measurement · 计算工作量 0.5 0.5
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 0.5 0.5
  合计 28 49.15

设计文档

需求分析

  1. 最短(经过站点数最少)路线查询,需要输出每次查询的详细路线与换乘情况。
  2. 换乘最少最短路线查询,需要输出每次查询的详细路线与换乘情况。
  3. 最快(经过站点数最少)遍历线路查询,需要输出遍历的详细路线。
  4. 用户只需要通过命令行进行以上三个操作。

功能设计

  1. 最短路线查询

    在命令行中以-b参数加两个地铁站点名称执行程序时,例如subway.exe -b station1 station2将计算从第一个站点station1到第二个站点station2的最短(经过的站点数最少)路线,并返回经过的站点的个数和路径,如果有换乘,请列出换乘的线路。输出格式如下:

      4
      知春路
      知春里
      海淀黄庄换乘地铁十号线
      中关村

     

  2. 换乘最少最短路线查询

    在命令行中以-c参数加两个地铁站点名称执行程序时,例如subway.exe -c station1 station2将计算从第一个站点station1到第二个站点station2的换乘最少的最短路线,并返回经过的站点的个数和路径,如果有换乘,请列出换乘的线路。输出格式同上。

  3. 最快遍历线路查询

    扩展命令行程序,使其以-a参数加一个地铁站点名称执行程序时,例如subway.exe –a station将计算从站点station出发,最快(经过的站点数最少,若一个站点经过多次需重复计算)地遍历地铁的所有车站的路线,要求

    a.换乘不出地铁系统,即不能从一个地铁口走到路面,然后从另一个站进去;

    b.只用经过一次,就算经过车站。

    程序输出总共经过多少站,以及经过的站名。举例来说,假如地铁系统只有知春路和西土城两个站,从知春路站出发,那么这个程序应该输出:

      3
      知春路
      西土城
      知春路

     

系统与模块设计

框架设计思路

 

建立三层系统架构。地图数据层用于读取文本中的地图信息,转化为使用邻接矩阵保存的图(Graph),并存储已查询过的路线信息以提高性能;线路规划层从交互控制层读取请求信息,使用相应算法对三种请求进行路线规划,并进行结果输出;交互控制层作为程序执行中心,进行相应的语法检查,转化字符串为相应请求数据传递给路线规划层。

模块设计

 

地图数据层(Map)
  1. 文本地图存储方式

    文本地图存储了地铁线路名称、各个地铁站点的名称以及车站换乘信息。为了方便人工直接查阅和正则表达式的解析,采用了较为简洁的存储方式,模板如下:

      @地铁线路名称
      站点1 站点2[其它途经地铁线1][其它途经地铁线2……] …… 站点n

     

    以地铁1号线为例:

      1号线
      苹果园 古城 八角游乐园 八宝山 玉泉路 五棵松 万寿路 公主坟[10号线] 军事博物馆[9号线] 木樨地 南礼士路 复兴门[2号线] 西单[4号线] 天安门西 天安门东 王府井 东单[5号线] 建国门[2号线] 永安里 国贸[10号线] 大望路[5号线] 四惠[八通线] 四惠东[八通线]
      @2号线
      ……

     

    注意地铁站名使用中文字符以外,其它所有字符均采用英文半角字符。站点信息中也应不存在空格符,比如东单[ 5号线]。使用@标志来标记环形线路。且由于机场线较为特殊,简化为三元桥-2号航站楼-3号航站楼的路线设定。

  2. 文本与程序数据转换

    对文本进行逐行读取,以2行为一个单位进行地铁信息录入,并存储于SubwayLine结构中。SubwayLine相关结构如下

      struct Station
      {
          public string Name;
          public bool IsTransferStation;
          public List<string> PlacedSubwayLineName;
      };
    
      struct SubwayLine
      {
          public string Name;
          public List<string> InLineSubwayStationsNames;
      };

     

    首先读取第一行的地铁线路名,然后使用空白符对第二行的站点信息进行切分。应用正则表达式对每个站点的信息进行提取,存入SubwayLine结构中,然后合并所有SubwayLine为一个Hashtable SubwayLines的排序列表。结合所有路线信息,创建另一个排序列表SoretedList Stations实现站点名与邻接矩阵下标的对应关系,不会创建重复的站点名键。

  3. 地铁图(Graph)的生成

    首先需要实现图的数据结构初步实现如下,

    public StationsGraph(SortedList stations, Hashtable subwayLines)
          {
              this.stationVertexNum = stations.Count;
              this.stationVertices = stations;
              this.adjacencyMatrix = new int[stationVertexNum, stationVertexNum];
              this.subwayLines = subwayLines;
              for (int i = 0; i < stationVertexNum; i++)
              {
                  for (int j = 0; j < stationVertexNum; j++)
                  {
                      adjacencyMatrix[i, j] = -1;
                  }
              }
              for (int i = 0; i < stationVertexNum; i++)
              {
                  adjacencyMatrix[i, i] = 0;
              }
              SetAdjacencyMatrix();
          }

     

    通过遍历SubwayLines排序列表读取每个站点所处所有线路的相邻站点信息,并且依据Stations的索引号实现邻接矩阵用于路线计算。需要建立两个地铁图,一个用于最短路径计算,一个用于最少换乘路径计算。

线路规划层(RoutePlanning)
  1. 最短路线规划

    采用Floyd最短路径算法进行计算,这个方法较为简单,一次计算所有的路径后存储于矩阵中。更重要的是如何在Floyd路径矩阵中寻找完整的路径。实现方式如下:

      private void searchCompletedShortestRoute(int station1Index, int station2Index, List<string> shortestRoute)
          {
              int shortestRouteInsertIndex;
              int pathStationIndex = routeMatrix[station1Index, station2Index][0];
    
              if (pathStationIndex == -1)
                  return;
              shortestRouteInsertIndex = shortestRoute.FindIndex((string stationName) => stationName == (string)map.Stations.GetKey(station2Index));
              shortestRoute.Insert(shortestRouteInsertIndex, (string)map.Stations.GetKey(pathStationIndex));
              searchCompletedShortestRoute(station1Index, pathStationIndex, shortestRoute);
              searchCompletedShortestRoute(pathStationIndex, station2Index, shortestRoute);
          }

     

    采用了递归的方式,不断在两点之间寻找最短中转点,直到两个点为相邻点。这样就可以通过Floyd路径矩阵找到完整的路径了。

  2. 最少换乘路线规划

    在这里又使用了递归的方式进行线路查找。首先在起始站点所在线路上搜索是否存在目标站点,如果存在则返回路径。否则,对这条地铁线上的所有中转站进行递归查找,查看下一个地铁线上是否存在目标站点。自此重复进行递归操作,知道查找到目标站点。值得注意的是,这里采用了两个方式优化性能。第一,对每层递归记录已经遍历的地铁线路,防止重复递归同一条线路。第二,采用了一个全局变量记录当前最浅的成功递归深度,进行剪枝操作,很大程度上优化了性能。

    private void searchLeastTransferRoute(int curRecursiveDepth, string curStationName, string curSubwayLineName, string targetStationName, List<string> notRecursiveSubwayLines, List<string> leastTransferRoute)
          {
              List<string> inLineSubwayStationsNames = ((SubwayLine)map.SubwayLines[curSubwayLineName]).InLineSubwayStationsNames;
              List<string> newNotRecursiveSubwayLines = new List<string>(notRecursiveSubwayLines.ToArray());
              List<string> newLeastTransferRoute = new List<string>(leastTransferRoute.ToArray());
    
              newNotRecursiveSubwayLines.Remove(curSubwayLineName);
              newLeastTransferRoute.Add(curStationName);
    
              if (recursiveDepthLimit != -1 && curRecursiveDepth > recursiveDepthLimit)
                  return;
    
              if (inLineSubwayStationsNames.Contains(targetStationName))
              {
                  recursiveDepthLimit = curRecursiveDepth;
                  newLeastTransferRoute.Add(targetStationName);
                  leastTransferRoutes.Add(newLeastTransferRoute);
                  return;
              }
    
              foreach (string stationName in inLineSubwayStationsNames)
              {
                  Station station = (Station)map.Stations[stationName];
                  if (station.IsTransferStation)
                      foreach (string subwayLineName in station.PlacedSubwayLineName)
                          if (newNotRecursiveSubwayLines.Contains(subwayLineName))
                              searchLeastTransferRoute(curRecursiveDepth + 1, stationName, subwayLineName, targetStationName, newNotRecursiveSubwayLines, newLeastTransferRoute);
              }
    
              return;
          }

     

    而后,需要进行第一次筛选,找出包含最少换乘站点的路线。在此处递归得到路线只是包含了相应换乘地铁站与始末站点,还要获取完整的路线规划。不同于“最短线路规划”,我们要直接从线路信息中直接提取两个换乘站点间的所有站点信息。得到完整路径后,进行第二次筛选,这里我们将筛选出最少换乘中的最短路径。自此完成路线规划。

输入输出控制(IO Controller)

为了简化程序,直接使用程序的主类进行的控制。较为简单,对相应的请求类型进行处理。有一定的容错控制,防止崩溃。

代码规范

默认采用:微软编程规范

性能测试与优化

最短线路规划功能性能测试

  1. 性能分析图

     

  2. 性能分析

    容易看到使用-b 南礼士路 新街口样例进行性能分析时,Floyd路径矩阵的生成(SubwayRoutePlanning.RoutePlanning::initFloyd)占据了将近一半的CPU消耗,消耗最大。这也比较符合O(n^3)的算法时间复杂度,是程序的核心算法部分。其它的外部代码算法,是相应的完整路径递归查找和相关的调用代码,虽然递归深度不是太深,但是一样消耗了40%的资源,足以看出递归的性能较为低下。

最少换乘最短线路规划功能性能测试

  1. 性能分析图

  2. 性能分析

    基本与“最短线路规划”性能消耗分布一致,“SubwayRoutePlanning.RoutePlanning::initFloyd”是性能消耗最大的函数。在这个功能中使用“剪枝”的方式进行优化。通过测试发现,未优化前程序整体消耗为10936毫秒,是剪枝后的20倍,足以见得剪枝对程序整体的优化之大。

测试

最短线路规划功能测试

  1. 基本运行测试

    -b 南礼士路 天安门西
    
    4
    南礼士路
    复兴门
    西单
    天安门西

     

  2. 同线路换乘节点-换乘节点测试

    -b 西直门 雍和宫
    
    5
    西直门
    积水潭
    鼓楼大街
    安定门
    雍和宫

     

  3. 单次换乘测试

    -b 军事博物馆 车公庄
    
    5
    军事博物馆
    白堆子
    白石桥南换乘地铁6号线
    车公庄西
    车公庄

     

  4. 多次换乘测试

    -b 南礼士路 新街口
    
    6
    南礼士路
    复兴门换乘地铁2号线
    阜成门
    车公庄换乘地铁6号线
    平安里换乘地铁4号线大兴线
    新街口

     

  5. 双线并行线路测试

    -b 大望路 高碑店
    
    4
    大望路
    四惠
    四惠东
    高碑店

     

    经测试发现换乘信息有误,双线并行情况需特殊处理。

      4
      大望路
      四惠
      四惠东换乘地铁八通线
      高碑店

     

  6. 超长线路规划测试

    -b 丰台东大街 望京东
    
    22
    丰台东大街
    七里庄
    六里桥换乘地铁10号线
    莲花桥
    公主坟换乘地铁1号线
    军事博物馆换乘地铁9号线
    白堆子
    白石桥南换乘地铁6号线
    车公庄西
    车公庄换乘地铁2号线
    西直门
    积水潭
    鼓楼大街换乘地铁8号线
    安德里北街
    安华桥
    北土城换乘地铁10号线
    安贞门
    惠新西街南口
    芍药居换乘地铁13号线
    望京西换乘地铁15号线
    望京
    望京东

     

    经仔细与北京地铁推荐线路比对,完全正确。

最少换乘最短线路规划测试

  1. 基本运行测试

    -c 南礼士路 天安门西
    
    4
    南礼士路
    复兴门
    西单
    天安门西

     

  2. 最少换乘对比最短路径测试

    -c 南礼士路 新街口
    
    7
    南礼士路
    复兴门
    西单换乘地铁4号线大兴线
    灵境胡同
    西四
    平安里
    新街口

     

    与最短线路规划的同一输入比较-c 南礼士路 新街口,符合最少换乘最短路径的功能要求。

  3. 超长线路规划测试

    -c 军事博物馆 惠新西街南口
    
    17
    军事博物馆
    西钓鱼台换乘地铁10号线
    慈寿寺
    车道沟
    长春桥
    火器营
    巴沟
    苏州街
    海淀黄庄
    知春里
    知春路
    西土城
    牡丹园
    健德门
    北土城
    安贞门
    惠新西街南口

     

    经测试发现缺少公主坟站,需要进行DEBUG。经过排查后发现是中间路径添加有误,遗漏添加无中间站的后继结点,修改后正确。

    17
      军事博物馆
      木樨地
      南礼士路
      复兴门
      西单
      天安门西
      天安门东
      王府井
      东单换乘地铁5号线
      灯市口
      东四
      张自忠路
      北新桥
      雍和宫
      和平里北街
      和平西桥
      惠新西街南口

     

线路站点查询功能测试

10号线

*巴沟
*苏州街
*海淀黄庄
*知春里
*知春路
*西土城
*牡丹园
*健德门
*北土城
*安贞门
*惠新西街南口
*芍药居
*太阳宫
*三元桥
*亮马桥
*农业展览馆
*团结湖
*呼家楼
*金台夕照
*国贸
*双井
*劲松
*潘家园
*十里河
*分钟寺
*成寿寺
*宋家庄
*石榴庄
*大红门
*角门东
*角门西
*草桥
*纪家庙
*首经贸
*丰台站
*泥洼
*西局
*六里桥
*莲花桥
*公主坟
*西钓鱼台
*慈寿寺
*车道沟
*长春桥
*火器营

 

此项功能较为简单,容易进行测试。

项目总结学习

不得不说这次学习收获颇丰,只有在科学的方法论的指导下才能发挥最大的生产力。按照PSP表的步骤进行规划统筹,而不是一味地直接编码,真切地可以保持整体思路的清晰。总结了一下几点:

  1. 计划优先

    到这个阶段的学习,不单单是对知识的探索,更多的是工作效率的提升。而任何工作都离不开计划和目标的设定,只有在具体的行动框架下才能规范自己的设计思路,督促自己完成每一个步骤。编码不是工作的一切,背后的思考与策划也十分重要。

  2. 适度放弃

    必须要承认在有限的知识积累和时间下,有些事情是无法完成的。在附加题的设计与编码上花费了8个小时左右的时间,可是到最后还是没有设计出最优化的方案,存在很多缺陷,由于测试量巨大,也无法精准排错。最后不得不放弃附加题。我认为这已经浪费了大量的时间,基本上在2小时内无法设计出完整方案就应该放弃这个计划,转向其他工作,这一个小的模块不是整个程序的核心部件,可有可无。认清自己的实力和条件,适时放弃才是提高效率的王道

  3. 文档化

    博客的撰写对项目思路的整理和自我的学习提升有着很大的帮助,脑子里的思路不是思路,只有能表达总结文档,甚至要考虑到别人能否从中有所收获才是真正地思考成果。而且这是对一个项目的总结,对自我的一种反馈,激发自己继续提升。

  4. 算法应用

    程序中用到了图相关的数个算法,不算难,但也是对过往学习的一次回顾。Folyd算法、递归查找、剪枝策略都是一次又一次的算法实操,重复融合实际应用与理论知识,认识到了理论知识对实际工程的重要性。要做一名优秀的软件工程师,必须要有足够的数学与计算机理论,才能在这条路上远走越远,而不是流于技术的表面。

  5. 技术速成

    为了软件工程课程顺利进行快速学习了博客搭建C#快速入门项目配置Markdown与HTML入门语法,虽然只是些皮毛但是随着问题的出现和解决,对技术的积累也在不断加深。在这个快速发展的时代,只有快速地学习才能适应身边环境的变迁。

欢迎关注我的个人博客:www.beyondbin.com

转载于:https://www.cnblogs.com/hyperleopard/p/5883326.html

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_33897722/article/details/93820532

智能推荐

Docker 快速上手学习入门教程_docker菜鸟教程-程序员宅基地

文章浏览阅读2.5w次,点赞6次,收藏50次。官方解释是,docker 容器是机器上的沙盒进程,它与主机上的所有其他进程隔离。所以容器只是操作系统中被隔离开来的一个进程,所谓的容器化,其实也只是对操作系统进行欺骗的一种语法糖。_docker菜鸟教程

电脑技巧:Windows系统原版纯净软件必备的两个网站_msdn我告诉你-程序员宅基地

文章浏览阅读5.7k次,点赞3次,收藏14次。该如何避免的,今天小编给大家推荐两个下载Windows系统官方软件的资源网站,可以杜绝软件捆绑等行为。该站提供了丰富的Windows官方技术资源,比较重要的有MSDN技术资源文档库、官方工具和资源、应用程序、开发人员工具(Visual Studio 、SQLServer等等)、系统镜像、设计人员工具等。总的来说,这两个都是非常优秀的Windows系统镜像资源站,提供了丰富的Windows系统镜像资源,并且保证了资源的纯净和安全性,有需要的朋友可以去了解一下。这个非常实用的资源网站的创建者是国内的一个网友。_msdn我告诉你

vue2封装对话框el-dialog组件_<el-dialog 封装成组件 vue2-程序员宅基地

文章浏览阅读1.2k次。vue2封装对话框el-dialog组件_

MFC 文本框换行_c++ mfc同一框内输入二行怎么换行-程序员宅基地

文章浏览阅读4.7k次,点赞5次,收藏6次。MFC 文本框换行 标签: it mfc 文本框1.将Multiline属性设置为True2.换行是使用"\r\n" (宽字符串为L"\r\n")3.如果需要编辑并且按Enter键换行,还要将 Want Return 设置为 True4.如果需要垂直滚动条的话将Vertical Scroll属性设置为True,需要水平滚动条的话将Horizontal Scroll属性设_c++ mfc同一框内输入二行怎么换行

redis-desktop-manager无法连接redis-server的解决方法_redis-server doesn't support auth command or ismis-程序员宅基地

文章浏览阅读832次。检查Linux是否是否开启所需端口,默认为6379,若未打开,将其开启:以root用户执行iptables -I INPUT -p tcp --dport 6379 -j ACCEPT如果还是未能解决,修改redis.conf,修改主机地址:bind 192.168.85.**;然后使用该配置文件,重新启动Redis服务./redis-server redis.conf..._redis-server doesn't support auth command or ismisconfigured. try

实验四 数据选择器及其应用-程序员宅基地

文章浏览阅读4.9k次。济大数电实验报告_数据选择器及其应用

随便推点

灰色预测模型matlab_MATLAB实战|基于灰色预测河南省社会消费品零售总额预测-程序员宅基地

文章浏览阅读236次。1研究内容消费在生产中占据十分重要的地位,是生产的最终目的和动力,是保持省内经济稳定快速发展的核心要素。预测河南省社会消费品零售总额,是进行宏观经济调控和消费体制改变创新的基础,是河南省内人民对美好的全面和谐社会的追求的要求,保持河南省经济稳定和可持续发展具有重要意义。本文建立灰色预测模型,利用MATLAB软件,预测出2019年~2023年河南省社会消费品零售总额预测值分别为21881...._灰色预测模型用什么软件

log4qt-程序员宅基地

文章浏览阅读1.2k次。12.4-在Qt中使用Log4Qt输出Log文件,看这一篇就足够了一、为啥要使用第三方Log库,而不用平台自带的Log库二、Log4j系列库的功能介绍与基本概念三、Log4Qt库的基本介绍四、将Log4qt组装成为一个单独模块五、使用配置文件的方式配置Log4Qt六、使用代码的方式配置Log4Qt七、在Qt工程中引入Log4Qt库模块的方法八、获取示例中的源代码一、为啥要使用第三方Log库,而不用平台自带的Log库首先要说明的是,在平时开发和调试中开发平台自带的“打印输出”已经足够了。但_log4qt

100种思维模型之全局观思维模型-67_计算机中对于全局观的-程序员宅基地

文章浏览阅读786次。全局观思维模型,一个教我们由点到线,由线到面,再由面到体,不断的放大格局去思考问题的思维模型。_计算机中对于全局观的

线程间控制之CountDownLatch和CyclicBarrier使用介绍_countdownluach于cyclicbarrier的用法-程序员宅基地

文章浏览阅读330次。一、CountDownLatch介绍CountDownLatch采用减法计算;是一个同步辅助工具类和CyclicBarrier类功能类似,允许一个或多个线程等待,直到在其他线程中执行的一组操作完成。二、CountDownLatch俩种应用场景: 场景一:所有线程在等待开始信号(startSignal.await()),主流程发出开始信号通知,既执行startSignal.countDown()方法后;所有线程才开始执行;每个线程执行完发出做完信号,既执行do..._countdownluach于cyclicbarrier的用法

自动化监控系统Prometheus&Grafana_-自动化监控系统prometheus&grafana实战-程序员宅基地

文章浏览阅读508次。Prometheus 算是一个全能型选手,原生支持容器监控,当然监控传统应用也不是吃干饭的,所以就是容器和非容器他都支持,所有的监控系统都具备这个流程,_-自动化监控系统prometheus&grafana实战

React 组件封装之 Search 搜索_react search-程序员宅基地

文章浏览阅读4.7k次。输入关键字,可以通过键盘的搜索按钮完成搜索功能。_react search