个人开发流程 --计应191第二小组牛振威
郑州地铁计费系统
PSP阶段 | 预计花费时间(小时) | 实际花费时间(小时) |
---|---|---|
计划 | 1 | 1 |
|
1 | 1 |
开发 | 9 | 14.5 |
|
2 | 4 |
|
1 | 0.5 |
|
1 | 3 |
|
3 | 3 |
|
1 | 2 |
|
1 | 2 |
计划:
在没有做需求分析前,刚拿到这个项目,觉得只需要几个简单的判断即可完成,所以在规划时间时需求分析所占时间并不高
需求分析:
制订计划时,觉得需求分析很快就能完成,2个小时绰绰有余,在真正开始做需求分析时才发现了很多问题:
根据百度到的结果,郑州地铁计费是按照公里来计费,由于无法得到每站的公里数,改为按站的数量进行计费
但是如果按照站计费,从起点站到终点站可以有很多不同的路线,那么应该选择哪一个?
最后决定按照最短的一条路径来计费,那么这个项目的重点就发生了改变:怎么求最短路径
具体设计:
根据以往javaweb项目开发的经验,第一个反应是设计数据库,经过多次修改最终留下了三个表:普通站点,带换乘的站点,线路
当设计完数据库后又是一头雾水,该怎么解决需要换乘的最短路径
如果只有一个换乘,那么我们可以根据根据数据库找到换乘点即可找到,如果有两个我们可以找到两条线上有的换乘点是否同时存在另外一个换乘点上,如果再多就不知道怎么解决了
因为第一个想法是这样的,让我陷入了严重的误区,找不到解决方法,经过思考,发现这种方法只能得到换乘最少的路线,并不是最短,显然不可行
随即想到了加坐标,从起点出发,每次都往距离终点更近的方向前进,如果遇到换乘点,走距离终点更近的那条,但是这个想法很快就被否定了,有很多情况不能适用
随后想了很多情况,但是都被一一否决,最后留下了一个保底的方案,算出所有请求,判断出最短,因为实在太过复杂,决定先百度学习别人的思路
百度后发现大都采用Dijkstra(迪杰斯特拉)和Floyd(弗洛伊德)算法,然后就去学习了这两个算法
在学习Dijkstra时突然觉得豁然开朗,我们并不需要在乎终点在哪,只要每次都走最短的路径,如果走到了终点,那这条路径就是最短路径
但是这个算法太复杂,同时还需要一个邻接矩阵,所以去学习了Floyd,拿这两个算法进行比较,Floyd显然简单了许多但是实现需要中间节点,算法的时间复杂度是立方阶,无法接受,最终决定使用Dijkstra
使用Dijkstra时又有一个一个问题,因为我们是按照站来计算,所以所有权都是1,而且顶点会非常的多,后来想是否可以用换乘点作为顶点,虽然顶点少了,但是每两个顶点的权都要自己手工计算,让我觉得很不舒服,最后放弃了这个想法
到这里才开始了真正的设计,前面的获取也可以算作为需求分析,现在就回到了最初的问题,是否需要数据库,由于这个项目并不需要扩展,也不需要考虑耦合度等问题,使用数据库只会增加难度,时间有限放弃了这种想法 那如果保存这些数据,首先就需要一个Station类(用来表示站点信息)那么如果保存这个数据,使用数组还是集合,如果使用数组那么就需要用下标表示站点在线路上的位置,虽然可行,但是查找起来太过于麻烦。既然相邻站点之间有联系,那么就可以采用 双向链表的数据结构,我们需要在Station类中添加两个私有属性,分别代表前驱节点和后继节点。站点信息就保存了下来,那么该怎么保存最短路径那,在使用这个算法时我们会计算出所有顶点的最短路径,那么可以使用Map,key来表示到底是哪个顶点的最短路径 value本来决定使用int来记录经过的站点数量,但是如果我们使用一个set也可以顺便记录经过的站点,到这里总算设计好可以开始编码了
具体编码 和 代码规范:
因为前面已经确定了使用何种算法,应该怎么保存数据,所以在具体编码的时候只需要套用别人写的算法,稍加修饰就完成了编码,代码规范因为平时习惯加上编译器的强大,花了很短的时间就搞定了
代码执行结果
代码复审
由于经验不足,担心不能看出问题,使用debug慢慢检查,对一些细节进行了修改,将之前一个很长的方法抽出了几个方法,同时由于编码时一心只想最短路径的问题,而忽略了当前项目的真实需求,计费。又添加了计费 算法,优化了输出结果
测试
由于前面花费了太多时间,没有时间完成很好的覆盖率,仅仅是随机抽取一些数据进行测试,在后期再进行拓展,或许能够发现新的问题
总结
因为没有做详细的测试,所以无法写出测试报告,后续再对其进行完善 写完这个项目后,计算自己所花时间,看到结果我是震惊的,原本觉得很简单的问题竟然花费了那么长的时间 ,原来觉得编码才是最花费时间的,结果需求分析和设计花费了更多的时间,事实上设计了很多可能也应该算作需求分析, 之前觉得需求分析只需要知道什么要求即可,现在发现要求里可以并不完整,可能那些隐藏的但是你需要完成的需求才是最难完成的
所以在拿到需求时不能仅仅靠第一感觉,要深入的思考,真正的需求是什么,如果需求就弄错了,那后续编写的代码也是无用代码
package dt; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; public class Line { public static List<Station> line1 = new ArrayList();//1号线 public static List<Station> line2 = new ArrayList();//2号线 public static List<Station> line3 = new ArrayList();//3号线 public static List<Station> line4 = new ArrayList();//4号线 public static List<Station> line5 = new ArrayList();//5号线 public static List<Station> line14 = new ArrayList();//14号线 public static List<Station> lineCj = new ArrayList();//城郊号线 public static Set<List<Station>> lineSet = new HashSet();//所有线集合 public static int stationCount = 0;//总的站点数量 static { init(); } /** * 初始化方法 */ private static void init() { String line1Str = "河南工业大学 郑大科技园 郑州大学 梧桐街 兰寨 铁炉 市民中心 西流湖 西三环 秦岭路 五一公园 碧沙岗 绿城广场 医学院 郑州火车站 二七广场 人民路 紫荆山 燕庄 民航路 会展中心 黄河南路 农业南路 东风南路 郑州东 博学路 市体育中心 龙子湖 文苑北路 河南大学新区"; String line2Str = "贾河 惠济区政府 毛庄 黄河迎宾馆 金洼 金达路 刘庄 柳林 沙门 北三环 东风路 关虎屯 黄河路 紫荆山 东大街 陇海东路 二里岗 南五里堡 花寨 南三环 站马屯 南四环"; String line3Str = "省体育中心 王砦 兴隆铺 同乐 南阳新村 海滩寺 大石桥 人民公园 二七广场 西大街 东大街 郑州文庙 博览中心 凤凰台 东十里铺 通泰路 西周 东周 省骨科医院"; String line4Str = " 省体育中心 北二十里铺 丰庆路 张家村 陈寨东 沙门 杨君刘 森林公园北 清华附中 龙湖中环北 龙湖北 龙湖南 龙湖中环南 农业东路 中央商务区 会展中心 商鼎路 东十里铺 货站街 七里河 果树所 金岱 姚庄 郎庄"; String line5Str = "月季公园 沙口路 海滩寺 郑州人民医院 黄河路 省人民医院 姚砦 众意西路 中央商务区 儿童医院 祭城 金水东路 郑州东 康宁街 省骨科医院 经开中心广场 福塔东 中原福塔 七里河 航海广场 城东南路 南五里堡 冯庄 京广南路 市第二人民医院 齐礼阎 后河芦 桐淮 陇海西路 市中心医院 五一公园"; String line14Str = "铁炉 市委党校 奥体中心"; String lineCjStr = "南四环 十八里河 沙窝李 双湖大道 小乔 华南城西 华南城 华南城东 孟庄 港区北 康平湖 兰河公园 恩平湖 综合保税区 新郑机场 郑州地铁6号线"; initList(line1, line1Str); initList(line2, line2Str); initList(line3, line3Str); initList(line4, line4Str); initList(line5, line5Str); initList(line14, line14Str); initList(lineCj, lineCjStr); } /** * 初始化列表,将字符串按空格分割添加到列表中 * * @param stationList 要初始化的列表 * @param lineStr 存放数据的字符串 */ private static void initList(List<Station> stationList, String lineStr) { String[] lineArr = lineStr.split(" "); for (String s : lineArr) { stationList.add(new Station(s)); } for (int i = 0; i < stationList.size() - 1; i++) { stationList.get(i).next = stationList.get(i + 1); stationList.get(i + 1).prev = stationList.get(i); } lineSet.add(stationList); stationCount += stationList.size(); } }
Station类
package dt; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Objects; /** * 使用双向链表保存数据 */ public class Station { private String name; //地铁站名称 public Station prev; //前一个站 public Station next; //后一个站 //本站到某一个目标站(key)所经过的所有站集合(value),保持前后顺序 private Map<Station, LinkedHashSet<Station>> orderSetMap = new HashMap(); public Station() { } public Station(String name) { this.name = name; } public String getName() { return name; } /** * 如果路线中已经存在则获取,不存在则创建路线 */ public LinkedHashSet<Station> getAllPassedStations(Station station) { if (orderSetMap.get(station) == null) { LinkedHashSet<Station> set = new LinkedHashSet(); set.add(this); orderSetMap.put(station, set); } return orderSetMap.get(station); } public Map<Station, LinkedHashSet<Station>> getOrderSetMap() { return orderSetMap; } /** * 如果名字相同就表示是同一个站点 */ @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Station station = (Station) o; return name.equals(station.name); } @Override public int hashCode() { return Objects.hash(name); } }
Subway类
package dt; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; /** * 使用迪杰斯特拉算法求出地铁最短路线 */ public class Subway { private List<Station> alreadyList = new ArrayList();//记录已经分析过的站点 /** * 计算两站之间最短路线,使用迪杰斯特拉算法 * * @param first 起点 * @param end 终点 */ public void calculate(Station first, Station end) { //循环计算,直到所有站点都被分析过结束 while (alreadyList.size() != Line.stationCount) { if (!alreadyList.contains(first)) { alreadyList.add(first); } //第一次使用起点初始化 if (first.getOrderSetMap().isEmpty()) { List<Station> stationList = getAllLinkedStations(first); //创建邻接矩阵 for (Station s : stationList) { first.getAllPassedStations(s).add(s); } } //获取当前节点的相邻节点 Station adjacent = getShortestPathStation(first); //如果相邻节点就是目标节点,将目标节点加入就可以退出循环 if (adjacent == end) { show(first, end); return; } //获取相邻节点的所有相邻节点 for (Station station : getAllLinkedStations(adjacent)) { if (alreadyList.contains(station)) { continue; } int shortestPath = first.getAllPassedStations(adjacent).size(); //获取station对应的路线,如果路线中已经存在child本身说明已经计算过child的距离 //如果这个站点在路线中已存在,并且比当前路线更长,那么就需要将其中的路线清除,在替换成新的路线 if (first.getAllPassedStations(station).contains(station) && (first.getAllPassedStations(station).size() - 1) > shortestPath) { first.getAllPassedStations(station).clear(); } //如果不满足上面的条件,说明现在这条路线更短,那么直接加入路径即可 first.getAllPassedStations(station).addAll(first.getAllPassedStations(adjacent)); first.getAllPassedStations(station).add(station); } alreadyList.add(adjacent); } show(first, end); //清除数据,一遍多次使用 clear(first); } /** * 清除数据 * @param station 需要被清除的起点 */ public void clear(Station station){ alreadyList.clear(); station.getOrderSetMap().clear(); } /** * 计费规则 */ public int charging(int stationCount) { if (stationCount <= 6) { return 2; } else if (stationCount <= 13) { return 3; } else if (stationCount <= 21) { return 4; } else { return (stationCount - 21) / 9 + 4; } } /** * 结果展示 */ public void show(Station first, Station end) { StringBuilder stringBuilder = new StringBuilder("共经过"); int stationCount = first.getAllPassedStations(end).size() - 1; stringBuilder .append(stationCount) .append("站,花费") .append(charging(stationCount)) .append("元") .append("\n"); for (Station station : first.getAllPassedStations(end)) { stringBuilder .append(station.getName()) .append("->"); } System.out.println(stringBuilder.substring(0, stringBuilder.length() - 2)); } /** * 获取最短距离当前站点最近的站点,因为权都为1,所以会有多个相同距离的结果 * 如果有相同,取遍历到的第一个 同时会创建不存在的路线 */ private Station getShortestPathStation(Station station) { int minLen = 65535; Station shortPathStation = null; for (Station s : station.getOrderSetMap().keySet()) { if (alreadyList.contains(s)) { continue; } //获取station到达当前目标需要经过的站 LinkedHashSet<Station> set = station.getAllPassedStations(s); //取出经过站数最少的那个 if (set.size() < minLen) { minLen = set.size(); shortPathStation = s; } } return shortPathStation; } /** * 获取与station相连的站点 * * @return 返回station相邻站点 */ private List<Station> getAllLinkedStations(Station station) { List<Station> stationList = new ArrayList(); for (List<Station> line : Line.lineSet) { if (line.contains(station)) { //循环遍历所有线,如果某条线包含station站点则去除station的相邻站点 Station s = line.get(line.indexOf(station)); if (s.prev != null) { stationList.add(s.prev); } if (s.next != null) { stationList.add(s.next); } } } return stationList; } public static void main(String[] args) { Subway subway = new Subway(); subway.calculate(new Station("河南工业大学"), new Station("华南城")); } }