地铁线路最短路径问题——2020软件工程个人项目1

关于求地铁线路最短路径问题

BK阿码农 2020年10月21日

1. 项目介绍

地铁线路图
  以北京地铁为例,地铁线路信息保存在data.txt中,格式如下:

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

2号线 西直门 积水潭 鼓楼大街 安定门 雍和宫 东直门 东四十条 朝阳门 建国门 北京站 崇文门 前门 和平门 宣武门 长椿街 复兴门 阜成门 车公庄 西直门

4号线大兴线 安河桥北 北宫门 西苑 圆明园 北京大学东门 中关村 海淀黄庄 人民大学 魏公村 国家图书馆 动物园 西直门 新街口 平安里 西四 灵境胡同 西单 宣武门 菜市口 陶然亭 北京南站 马家堡 角门西 公益西桥 新宫 西红门 高米店北 高米店南 枣园 清源路 黄村西大街 黄村火车站 义和庄 生物医药基地 天宫院

5号线 天通苑北 天通苑 天通苑南 立水桥 立水桥南 北苑路北 大屯东路 惠新四街北口 惠新四街南口 和平西桥 和平里北街 雍和宫 北新桥 张自忠路 东四 灯市口 东单 崇文门 磁器口 天坛东门 蒲黄榆 刘家窑 宋家庄

6号线 海淀五路居 慈寿寺 花园桥 白石桥南 车公庄西 车公庄 平安里 北海北 南锣鼓巷 东四 朝阳门 东大桥 呼家楼 金台路 十里堡 青年路 褡裢坡 黄渠 常营 草房 物资学院路 通州北关 北运河西 郝家府 东夏园 潞城

7号线 北京西站 湾子 达官营 广安门内 菜市口 虎坊桥 珠市口 桥湾 磁器口 广渠门内 广渠门外 九龙山 大郊亭 百子湾 化工 南楼梓庄 欢乐谷景区 双合 焦化厂

8号线 朱辛庄 育知路 平西府 回龙观东大街 霍营 育新 西小口 永泰庄 林萃桥 森林公园南门 奥林匹克公园 奥林中心 北土城 安华桥 安德里北街 鼓楼大街 什刹海 南锣鼓巷 中国美术馆

8号线南段 珠市口 天桥 永定门外 木樨园 海户屯 大红门南 和义 东高地 火箭万源 五福堂 德茂 瀛海

9号线 郭公庄 丰台科技园 科怡路 丰台南路 丰台东大街 七里庄 六里桥 六里桥东 北京西站 军事博物馆 白堆子 白石桥南 国家图书馆

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

13号线 西直门 大钟寺 知春路 五道口 上地 西二旗 龙泽 回龙观 霍营 立水桥 北苑 望京西 芍药居 光熙门 柳芳 东直门

14号线东段 善各庄 来广营 东湖渠 望京 阜通 望京南 将台 东风北桥 枣营 朝阳公园 金台路 大望路 九龙山 平乐园 北工大西门 十里河 方庄 蒲黄榆 景泰 永定门外 北京南站

14号线西段 西局 七里庄 大井 郭庄子 大瓦窑 园博园 张郭庄

15号线 俸伯 顺义 石门 南法信 后沙峪 花梨坎 国展 孙河 马泉营 崔各庄 望京东 望京 望京西 关庄 大屯路东 安立路 奥林匹克公园 北沙滩 六道口 清华东路西口

16号线 北安河 温阳路 稻香湖路 屯佃 永丰 永丰南 西北旺 马连洼 农大南路 西苑

八通线 四惠 四惠东 高碑店 传媒大学 双桥 管庄 八里桥 通州北苑 果园 九棵树 梨园 临河里 土桥

昌平线 昌平西山口 十三陵景区 昌平 昌平东关 北邵洼 南邵 沙河高教园 沙河 巩华城 朱辛庄 生命科学园 西二旗

房山线 阎村东 苏庄 良乡南关 良乡大学城西 良乡大学城 良乡大学城北 广阳城 篱笆房 长阳 稻田 大葆台 郭公庄

首都机场线 T3航站楼 T2航站楼 三元桥 东直门

西郊线 巴沟 颐和园西门 茶棚 万安 植物园 香山

燕房线 阎村东 紫草坞 阎村 星城 大石河东 马各庄 饶乐府 房山城关 燕山

亦庄线 宋家庄 肖村 小红门 旧宫 亦庄桥 亦庄文化园 万源街 荣京东街 荣昌东街 同济南路 经海路 次渠南 次渠 亦庄火车站


2. 主要功能

  • 查询站点信息:

  查询时间、站点所在线路集、站点是否为换乘站等。

  • 查询地铁线路信息:

  查询时间、线路所包含的站点等。

  • 查询两站点之间的最短(最少经过站数)乘车路线:

  查询时间、具体途径路线、具体换乘站点、经过站点总数、换乘总数等。

  • 打印导出查询内容:

  导出并自动记录电脑本地时间。


3. 需求分析

  • 功能需求:

  为使用者提供 “指 定” 两站的最短乘车路线,并展示线路内的具体途径车站。极大方便使用者,节省路上时间,避免走远路。

  • 性能需求:

  需要具备极快的反应速度,准确高效的为使用者提供最短乘车路线。

  • 可靠性与可用性需求:

  确保用户能用较短的时间乘坐在两站之间。

  • 出错处理需求:

  若遇到多条线路,结合拥挤指数,推算出较优路线置信度,只提供最优的几条线路,以此降低出错率。

  • 接口需求:

  通信接口:需要及时得到线路拥挤指数与车次信息。

  • 约束:

  线路的长短与实时拥挤程度需要及时平衡协调,没有完全确定的最优线路

  • 逆向需求:

  软件不应该建议用户尝试置信度较低的路线,较少的推荐用户使用拥挤路线。


4. 实现语言

  • 编程语言:

  Java语言

  • IDE:

  Eclipse2019-06


5. 实现算法

Dijkstra算法 (查看百度百科)

  • 概念:

  从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。

  • 主要特点:

  是从起始点开始,采用贪心算法的策略,每次遍历到始点距离最近且未访问过的顶点的邻接节点,直到扩展到终点为止。


6. 类职责划分

类名 方法 实现
Main_Util - 程序入口,基本方法实现
Main_Util static void main 内设多个循环,通过关键词来进行操作
ReadText - 读取文件的操作
ReadText public static Graph LoadSubwayMap(String path) 通过路径String,读取txt内容,生成地铁线路图
Subway_Main - 基本的查询操作类,附属许多查询操作方法
Subway_Main PrintMenu() 输出菜单1,包含1.读取本地地址与2.退出
Subway_Main PrintMenu2() 输出菜单2,包含1.查询路线2.查询站点3.查询站点之间最短路径4.退出
Subway_Main WriteFileJump(String s) 意思为写文件跳出此类(s地址),包含1.是否保存查询记录2.不保存
Subway_Main Map<String, List> LinesMap 多条地铁线路组成的哈希图
Subway_Main Map<String, Integer> StationName_Id 获取站点名转换站点id的哈希图
Subway_Main Map<Integer, String> stationId_NameMap 获取站点id转换站点名的哈希图
Subway_Main Map<String, Staition> StationName_Station 获取站点名转换站点的哈希图
Subway_Main Graph LoadSubwayMap(String path) 生成图函数
Subway_Main String Lineinformation(String inner) 查询地铁线路详情信息
Subway_Main StationQ(Graph graph,String inner) 查询地铁站点详情信息
Subway_Main List FindPublicStations(String station1, String station2) 查找两个站点公共线路上的站点
Subway_Main StationsQuery(Graph graph,String inner) 查询两个站点之间的最短路径
Line_Station - 定义站点,以图、边为单位连接成图
Line_Station Station 定义地铁站点(名称、线路、是否换乘)
Line_Station Graph 定义基本线路站点图信息(多点集、多边集)
Line_Station Graph-Edge 定义图的边、两点连成边、判断是否为边的方法
Line_Station Graph-CreatG(List<Line_Station.Station>vertics,List edges) 图的创建方法
WriteResult - 保存输出的大类,写出文件
WriteResult SavingResults(String Retuslt,String SavingPath) 将Retuslt的String内容,通过SavingPath路径保存

7. 核心代码

  • 主运行函数Main_Util :

public class Main_Util {
	static Graph graph;
	public static void main(String[] args) throws Exception{
		System.out.println("欢迎使用地铁线路查询(计算1802 金哲仑)");
		while(true) {//大菜单外循环
			Subway_Main.PrintMenu();//菜单输出类型1
			Scanner in=new Scanner(System.in);
			String inner=null;
			inner=in.nextLine();
			if(inner.contains("Exit")||inner.contains("exit")) {//退出操作
				System.out.println("退出系统!谢谢使用!");
				System.exit(0);
			}else if(inner.contains("Loading:")||inner.contains("loading:")) {//获取加载文件地址
				String path=inner.substring(inner.indexOf(':')+1,inner.length());//删除冒号前的内容
				graph=Subway_Main.LoadSubwayMap(path);//根据.txt文本内容生成图
				while(true) {//生成图后的内循环
					Subway_Main.PrintMenu2();//菜单输出类型2
					inner=null;
					inner=in.nextLine();//读取控制台输入
					String string;
					if(inner.contains("Line:")) {//查询线路详情操作
						string=Subway_Main.Lineinformation(inner);
						if(!string.equals(""))//如果获取为非空,执行写出文件操作
							Subway_Main.WriteFileJump(string);
					}else if(inner.contains("Query:")) {//查询两站之间最短线路操作
						string=Subway_Main.StationsQuery(graph,inner);
						if(!string.equals(""))//如果获取为非空,执行写出文件操作
							Subway_Main.WriteFileJump(string);
					}else if(inner.contains("Station:")) {//查询站点详情操作
						string=Subway_Main.StationQ(graph,inner);
						if(!string.equals(""))//如果获取为非空,执行写出文件操作
							Subway_Main.WriteFileJump(string);
					}else if(!inner.contains("Exit")&&!inner.contains("exit")) {//重复循环操作,保留生成图
						System.out.println("请输入菜单所示正确指令");
					}else {//退出循环操作
						System.out.println("退出系统!谢谢使用!");
						System.exit(0);
					}
				}
			}else {//判断是否输入正确路径
				System.out.println("请先输入\"Loading:\"导入线路txt文件");
			}
		}	
	}
}
  • 基本线路结构定义Line_Station:

public class Line_Station {
	//用于定义点、线、图的List集合
	//定义构造点、线的方法,定义创建图的方法
	public static class Staition{//定义地铁站点
		String stationName;//线路站点名称
		List<String> Lines;//所在线路<字符串>
		boolean IfTransfer=false;//是否为换乘站
	}
	public static class Graph{//基本线路站点图信息
		public List<Line_Station.Staition> Vertics=new ArrayList<>();//多个点定义集
		public List<List<Edge>> Edges=new ArrayList<List<Edge>>();//多个边定义集
		public Graph() {//构造图的无参方法
		}
		public Graph(List<Line_Station.Staition>verG,List<Edge>edgG) {
			CreatG(verG, edgG);//定义创建图方法
		}
		public static class Edge{//定义
			public int u,v;//定义两点之间连线为边
			public Edge() {//构造边的无参方法
			}
			public Edge(int u,int v) {//定义两点连成边
				this.u=u;
				this.v=v;
			}
			public boolean equal(Edge edges) {//判断是否为两站点边的方法
				if(this.u==edges.u&&this.v==edges.v)return true; //连线两站点确定
				else return false;
			}
		}
		public void CreatG(List<Line_Station.Staition>vertics,List<Edge> edges) {
			//构建地铁图线路图
			this.Vertics=vertics;//传递地铁站点集
			for(int i=0;i<Vertics.size();i++) {//增加地铁线路集
				Edges.add(new ArrayList<Edge>());
			}
			for(int i=0;i<edges.size();i++) {//遍历站点连线边集,链接创建图
				Edges.get(edges.get(i).u).add(edges.get(i));
			}
		}
	}
}
  • 读取文本ReadText:

public class ReadText {
	static Graph graph;
	public static Map<String, List<Staition>> LinesMap =new HashMap<String, List<Staition>>();//多条地铁线路组成的哈希图
	public static Map<String, Integer> StationName_Id=new HashMap<String, Integer>();//获取站点名转换站点id的哈希图
	public static Map<Integer, String> stationId_NameMap=new HashMap<Integer, String>();//获取站点id转换站点名的哈希图
	public static Map<String, Staition> StationName_Station=new HashMap<String, Staition>();//获取站点名转换站点的哈希图
	public static Graph LoadSubwayMap(String path) {//生成图函数
		System.out.println("正在读取路径"+path+"的内容");
		List<Staition> Vertics = new ArrayList<>();//设置站点集
		List<Line_Station.Graph.Edge> Edges = new ArrayList<>();//设置边集
		try {
			BufferedReader buffer=new BufferedReader(new InputStreamReader(new FileInputStream(new File(path)),"UTF-8"));//初始化字符输入流
			String Line=null;
			int StationNum=0;//记录站点个数
			while((Line=buffer.readLine())!=null) {
				String[] list=Line.split(" ");//以空格为分格
				List<Line_Station.Staition> stations=new ArrayList<>();
				String LineName =list[0];
				int bef=0;//前点
				int now=0;//后点,为记录相连两点
				for(int i=1;i<list.length;i++) {
					String StationName=list[i];
					Line_Station.Staition station;
					if(!StationName_Id.containsKey(StationName)) {//判断新站点,是否初始化站点
						station=new Staition();
						StationName_Station.put(StationName, station);//站点名映射站点
						station.stationName=StationName;
						station.Lines=new ArrayList<>();
						station.Lines.add(LineName);
						Vertics.add(station);
						StationName_Id.put(StationName, StationNum);//站点名映射站点id
						stationId_NameMap.put(StationNum, StationName);//站点id映射站点名
						StationNum+=1;
					}else {//非新站点则记录进入线路信息
						station =StationName_Station.get(StationName);
						station.Lines.add(LineName);
					}
					if(station.Lines.size()>=0)	station.IfTransfer=true;//记录多条线路的站点为换乘站点
					stations.add(station);
					if(i==1) {
						now=StationName_Id.get(StationName);//开始站点
					}else {
						bef=now;
						now=StationName_Id.get(StationName);
						Edges.add(new Line_Station.Graph.Edge(bef,now));//记录边信息
						Edges.add(new Line_Station.Graph.Edge(now,bef));
					}
				}
				LinesMap.put(LineName, stations);//记录线路名与多站点名映射
			}
			buffer.close();
		} catch (Exception e) {
			// TODO: handle exception
			System.out.println("文件读取错误:"+e);
			System.exit(0);
		}
		graph=new Graph(Vertics, Edges);
		System.out.println("文件已成功生成图!!");
		return graph;
	}
}
  • 实现主要查询功能函数Subway_Main:

public class Subway_Main {
	public static void PrintMenu() {//外循环大菜单
		System.out.println("1.请输入\"Loading:\"+本地地址,读取本地地铁线路txt文件");
		System.out.println("2.请输入\"Exit\"退出查询系统");
	}
	public static void PrintMenu2() {//图内循环中菜单
		System.out.println("地铁路线已获取,请使用以下关键词:");
		System.out.println("1.请输入\"Query:\"+站点名(空格)站点名,进行路线查询");
		System.out.println("2.请输入\"Line:\"+线路名,查询线路所含站点");
		System.out.println("3.请输入\"Station:\"+站点名,查询该站点具体信息");
		System.out.println("4.请输入\"Exit\"退出查询系统");
	}
	public static void WriteFileJump(String s) throws IOException {//写文件方法
		System.out.println("是否需要保存查询记录:");
		System.out.println("1.请输入\"Saving:\"+本地地址,保存在本地txt文件");
		System.out.println("2.输入\"NO\"不保存");
		Scanner in=new Scanner(System.in);
		String inner="";//写入初始化
		String SavingPath="";//保存路径初始化
		while(true) {
			inner=in.nextLine();
			if(inner.contains("Saving")) {//判断关键词Saving
				SavingPath=inner.substring(inner.indexOf(':')+1,inner.length());//截取冒号以后的内容(路径)
				WriteResult.SavingResults(s, SavingPath);
				break;
			}else if(inner.contains("NO")||inner.contains("no")||inner.contains("No")) {//判断类似关键词no
				System.out.println("放弃保存,结束此次查询");
				break;
			}else {
				System.out.println("请输入正确的关键词");//循环,直至输入正确关键词或选择不保存
			}
		}
	}
	public static Map<String, List<Staition>> LinesMap =new HashMap<String, List<Staition>>();//线路名与站点集的哈希映射
	public static Map<String, Integer> StationName_Id=new HashMap<String, Integer>();//站点名和站点id的哈希映射
	public static Map<Integer, String> stationId_NameMap=new HashMap<Integer, String>();//站点id和站点名的哈希映射
	public static Map<String, Staition> StationName_Station=new HashMap<String, Staition>();//站点名和站点类(自身)的哈希映射
	public static Graph LoadSubwayMap(String path) {//加载地铁生成图的方法
		System.out.println("正在读取路径"+path+"的内容");
		List<Staition> Vertics = new ArrayList<>();//地铁站点集
		List<Line_Station.Graph.Edge> Edges = new ArrayList<>();//站点组成的边集
		try {
			BufferedReader buffer=new BufferedReader(new InputStreamReader(new FileInputStream(new File(path)),"UTF-8"));//设置字符输入流
			String Line=null;
			int StationNum=0;
			while((Line=buffer.readLine())!=null) {//判断是否写入
				String[] list=Line.split(" ");//根据空格分割内容
				List<Line_Station.Staition> stations=new ArrayList<>();
				String LineName =list[0];
				int bef=0;int now=0;
				for(int i=1;i<list.length;i++) {
					String StationName=list[i];
					Line_Station.Staition station;
					if(!StationName_Id.containsKey(StationName)) {
						station=new Staition();
						StationName_Station.put(StationName, station);//设置映射关系
						station.stationName=StationName;//赋站点名初值
						station.Lines=new ArrayList<>();//赋站点线路初值
						station.Lines.add(LineName);//赋站点名线路名
						Vertics.add(station);
						StationName_Id.put(StationName, StationNum);//赋站点名和站点id映射关系
						stationId_NameMap.put(StationNum, StationName);//赋站点id和站点名映射关系
						StationNum+=1;
					}else {
						station =StationName_Station.get(StationName);
						station.Lines.add(LineName);
					}
					if(station.Lines.size()>1)
						station.IfTransfer=true;
					stations.add(station);
					
					if(i==1) {
						now=StationName_Id.get(StationName);
					}else {
						bef=now;
						now=StationName_Id.get(StationName);
						Edges.add(new Line_Station.Graph.Edge(bef,now));
						Edges.add(new Line_Station.Graph.Edge(now,bef));
					}
				}
				LinesMap.put(LineName, stations);
			}
			buffer.close();
		} catch (Exception e) {
			// TODO: handle exception
			System.out.println("文件读取错误:"+e);
			System.exit(0);
		}
		Graph graph=new Graph(Vertics, Edges);
		System.out.println("文件已成功生成图!!");
		return graph;
	}
	public static String Lineinformation(String inner) {//查询线路的方法类
		String QueryLineName=inner.substring(inner.indexOf(':')+1,inner.length());//获取冒号后面的线路名
		String resultString="";//初始化输出字符串
		try {
			if(!LinesMap.containsKey(QueryLineName)) {//若不包含任何线路名
				throw new Exception("查询不到"+QueryLineName);//抛出错误
			}
			System.out.println(QueryLineName+"详情信息:");
			List<Staition> stations=LinesMap.get(QueryLineName);//获取线路名与线路站点集的哈希映射
			resultString=QueryLineName+":";
			for(Staition station:stations) resultString+=" "+station.stationName;//遍历线路站点集,依次输出
			System.out.println(resultString);
		} catch (Exception e) {
			// TODO: handle exception
			System.out.println(e);
		}
		return resultString;
	}
	public static String StationQ(Graph graph,String inner) {//查询站点详情信息方法
		String StationName=inner.substring(inner.indexOf(':')+1,inner.length());//获取冒号后面的站点名
		String resultString="";
		try {
			if(!StationName_Id.containsKey(StationName)) {//若不包含任何站点名
				throw new Exception("查询不到"+StationName);//抛出错误
			}
			System.out.println(StationName+"详情信息:");
			Staition stationQ=StationName_Station.get(StationName);//获取站点类实体与站点名的哈希映射
			
			int countTransfer=0;//初始化换乘线路次数
			for(String s:stationQ.Lines) {//遍历站点的线路集内容
				resultString+="所在线路:"+s+"\n";
				countTransfer++;//换乘+1
			}
			if(countTransfer<=1) {//只有一条线路经过
				resultString+="并非换乘站台";
			}else {//多线路经过,可换乘
				resultString+="为换乘站台";
			}
			System.out.println(resultString);
			
		} catch (Exception e) {
			// TODO: handle exception
			System.out.println(e);
		}
		return resultString;
	}
	public static List<String> FindPublicStations(String station1, String station2){//查找两个站点公共线路上的站点
		List<String> Line1=StationName_Station.get(station1).Lines;//获取站点名与站点实体的映射内容
		List<String> Line2=StationName_Station.get(station2).Lines;//获取站点名与站点实体的映射内容
		List<String> Line2EXIST=new ArrayList(Line2); 
		List<String> Line2NOTEXIST=new ArrayList(Line2); 
		Line2EXIST.removeAll(Line1);//剔除Line1与Line2的公共站点O(=Line2-Line1&&Line2)
		Line2NOTEXIST.removeAll(Line2EXIST);//剔除Line2除公共站点以外的站点(=Line2-(Line2-Line1&&Line2))
		return Line2NOTEXIST;//=Line1&&Line2
	}
	public static String StationsQuery(Graph graph,String inner) {//查询两点间最短路径的核心方法代码
		String Begin=inner.substring(inner.indexOf(':')+1,inner.indexOf(" "));//获取冒号后空格前的内容(站点1)
		String End=inner.substring(inner.indexOf(" ")+1,inner.length());//获取空格后的内容(站点2)
		System.out.println("查询"+Begin+"与"+End+"的最短线路:");
		try {
			if(!StationName_Id.containsKey(Begin)) {//判断是否存在输入的站点1
				throw new Exception("未找到"+Begin+"站");
			}
			if(!StationName_Id.containsKey(End)) {//判断是否存在输入的站点2
				throw new Exception("未找到"+End+"站");
			}
			if(Begin.equals(End)) {////判断站点1与站点2是否相同
				throw new Exception("查询两站相同");
			}
		} catch (Exception e) {
			// TODO: handle exception
			System.out.println(e);
		}
		
		int TotalVertics=graph.Vertics.size();//总结点长
		int[] AllShortDistance=new int[TotalVertics];//出发站到各站点的最短距离
		int[] AllLeastChansfer=new int[TotalVertics];//出发站到各站点的最少换乘次数
		String[] AllLine=new String[TotalVertics];//出发站到各站点的最短路径途径站记录
		int[] AllBeforeStation=new int[TotalVertics];///出发站到各站点的前一个途径站点id
		boolean[] AllIfVisited = new boolean[TotalVertics];//检查各站点途径记录
		
		for(int i=0; i<TotalVertics; i++) {//各点初始化 
			AllShortDistance[i] = Integer.MAX_VALUE; //初始距离无限大
			AllLeastChansfer[i] = 0;//初始换乘0次
		}
		//起点初始化
		AllShortDistance[StationName_Id.get(Begin)]=0;//起始距离0
		AllBeforeStation[StationName_Id.get(Begin)]=-1;//无途径站点
		
		int CountNum=0;//记录扫描站点
		while(CountNum<TotalVertics) {
			int RecentV=0;//当前查到的最近站点id
			int MinDistance = Integer.MAX_VALUE;//当前最短距离
			for(int i=0;i<TotalVertics; i++) {  //选择当前距离最近的站点
				if((!AllIfVisited[i]) && (AllShortDistance[i]<MinDistance)) {
					RecentV = i;//赋值最近站点id
					MinDistance = AllShortDistance[i];//赋值最近站点距离
				}
			}
			AllIfVisited[RecentV] = true;  //将该站点设置为已访问
			CountNum++;
			List<Graph.Edge> Edges=graph.Edges.get(RecentV);//获取该站点邻接关系
			for(Graph.Edge edge:Edges) {//遍历边集,筛选标准线路站点少,且换乘少
				if(!AllIfVisited[edge.v]&&AllShortDistance[RecentV]+1<AllShortDistance[edge.v]) {//寻找非访问过的,且路径小的线路
					List<String> StayLine=FindPublicStations(stationId_NameMap.get(edge.u), stationId_NameMap.get(edge.v));
					AllLine[edge.v]=StayLine.get(0);//更新临近点线路
					if(!stationId_NameMap.get(edge.u).equals(Begin)) {//判断最近点是否为起点
						if(AllLine[edge.u]!=AllLine[edge.v]) {//判断是否在同一线路,不在一条线路,换乘+1
							AllLeastChansfer[edge.v]=AllLeastChansfer[edge.u]+1;
						}
						else {
							AllLeastChansfer[edge.v]=AllLeastChansfer[edge.u];
						}
					}
					AllShortDistance[edge.v]=AllShortDistance[RecentV]+1;//更新最短路径距离(+1)
					AllBeforeStation[edge.v]=edge.u;
				}else if(!AllIfVisited[edge.v]&&AllShortDistance[RecentV]+1==AllShortDistance[edge.v]) {//当距离一致时,考虑最少换乘
					List<String> StayLine=FindPublicStations(stationId_NameMap.get(edge.u), stationId_NameMap.get(edge.v));
					AllLine[edge.v]=StayLine.get(0);
					if(!stationId_NameMap.get(edge.u).equals(Begin)) {
						if(AllLine[edge.u]!=AllLine[edge.v]) {//判断是否在同一条线路
							if(AllLeastChansfer[edge.u]+1<AllLeastChansfer[edge.v]) {//两站点不在同一线路,距离+1,换乘+1,设置前站点
								AllLeastChansfer[edge.v]=AllLeastChansfer[edge.u]+1;
								AllShortDistance[edge.v]=AllShortDistance[RecentV]+1;
								AllBeforeStation[edge.v]=edge.u;
							}
						}else {
							if(AllLeastChansfer[edge.u]<AllLeastChansfer[edge.v]) {//若两站点在同一线路,将距离+1,换乘不+1,设置前站点
								AllLeastChansfer[edge.v]=AllLeastChansfer[edge.u];
								AllShortDistance[edge.v]=AllShortDistance[RecentV]+1;
								AllBeforeStation[edge.v]=edge.u;
							}
						}
					}
				}
			}
		}
		int StationCount=0;//初始化途径站点数
		int TransferCount=0;//初始化换乘次数
		ArrayList<Integer> ReversePath=new ArrayList<>();//记录逆序输出顺序(AllBeforeStation顺序相反)
		int next =StationName_Id.get(End);//获取终点站id
		while(AllBeforeStation[next]!=-1) {//遍历直至起始站点
			StationCount++;//增加途径站
			ReversePath.add(next);//增加在输出站点集里
			next=AllBeforeStation[next];//获取前站,持续循环寻找前站
		}
		
		String ShortPath=Begin+"—>"+stationId_NameMap.get(ReversePath.get(ReversePath.size()-1));//输出格式(起始->终点)
		String PresentLine=FindPublicStations(Begin, stationId_NameMap.get(ReversePath.get(ReversePath.size()-1))).get(0);//获取路线
		
		for(int i=ReversePath.size()-2;i>0;i--) {//输出线路
			String nameString=stationId_NameMap.get(ReversePath.get(i));
			String lastString=stationId_NameMap.get(ReversePath.get(i+1));
			List<String> LocalLine=FindPublicStations(nameString, lastString);
			if(!LocalLine.get(0).equals(PresentLine)) {//判断是否有线路换乘
				ShortPath+="->\n换乘"+LocalLine.get(0)+"\n";
				PresentLine=LocalLine.get(0);
				TransferCount++;
			}
			ShortPath+="->"+nameString;
		}
		StationCount--;
		ShortPath+="->"+End;
		ShortPath+="\n共经过:" + StationCount +"站(不包含查询两站点)\n"
				+ "共换乘:" + TransferCount + "次";
		System.out.println("成功查询"+Begin+"与"+End+"两站间的最短线路:");
		System.out.println(ShortPath);
		return ShortPath;
	}
}
  • 输出查询结果文件WriteResult:

public class WriteResult {
	public static void SavingResults(String retuslt,String Savingpath)throws IOException{
		StringReader saving=new StringReader(retuslt);//记录的文件路径
		BufferedReader buffer=new BufferedReader(saving);//创建字符输入流
		String string;
		if(Savingpath.equals("")) {//判断文件路径是否为空
			throw new IOException("保存路径为空");
		}else {
			FileWriter file=new FileWriter(Savingpath,true);//创建写文件方法
			BufferedWriter writer=new BufferedWriter(file);
			writer.newLine();//新建一行
			Date date = new Date();//记录本机本地时间
			SimpleDateFormat dateFormat= new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");//设置时间输出格式
			writer.write("查询时间:"+dateFormat.format(date));//写入查询时间
			writer.newLine();//换行
			while((string=buffer.readLine())!=null) {//设置
				System.out.println("路径正确,正在保存中...");
				writer.write(string);//分行写入
				writer.newLine();
			}
			buffer.close();
			writer.close();
			System.out.println("保存成功!");
		}
	}
}

8. 测试用例

(1)大菜单(外循环)

  • 实现输入文件地址并生成地铁结构图,进入下次循环:

  • 实现输入格式有误与退出系统操作:

(2)中菜单(图内循环)

  • 查询两站点之间最短线路:

  • 找不到其中站点:

  • 起点重点相同:

  • 查询线路操作:

  • 找不到该线路:

  • 查询站点操作:

  • 退出系统:

  • 输入错误:

(3)保存操作

  • 确认保存,输入保存文件地址:

  • 不保存

(4)保存文件内部

  • 内部文件展示:


9. 总结

  • 对于高级JAVA的数据结构更加熟悉并掌握使用方法:ArrayListHashMap等。

  • 第一次尝试Dijkstra算法的实现,并通过代码的实践收获了更多算法详情内容。

  • 学习JAVA中的IO实现,能准确读取并修改文件内容。

  • 对于类的调用以及多类的合作交互有了更好的了解。

  • 使用MarkDown编写博客,体验非常丰富,并且调试UI注意阅读舒适度。

  • 源文件已上传至Github:SoftWare-Engineering-Subway

posted @ 2020-10-21 20:16  BK阿码农  阅读(548)  评论(1)    收藏  举报