北京地铁线路规划
北京地铁线路规划
仓库代码:https://github.com/arthurcao99/SubwayHelper
地铁出行路线规划设计:https://www.cnblogs.com/caolx/p/11558996.html
1.结构概述
该项目分为Control、Main、Model这三个模块。Control模块用于存放文件读取和计算路径代码,Main模块用于存放主函数,Model用于存放各个实体类,如Station类、Line类等。
2.算法使用
由于开始规划这个项目时,未仔细阅读任务要求,错误认为该项目需要利用Dijkstra最短路径算法来求出两个站点之间的最短距离,后发现该程序的要求仅仅是最少站点,对换乘次数并未作具体要求,因此采用了BFS广度优先搜索算法来计算两个站点之间最短路径。
3.具体代码分析
3.1 Model模块
3.1.1 Station
private String stationName; //用于存放站点名称
private LinkedList<Station> connectedStation; //用于存放该站点相邻站点,类似于邻接表结构
private Boolean isChangingStation=false; //用于判断该站点是否为换乘站点
3.1.2 Line
private String lineName; //用于存放线路名称
private ArrayList<Station> stations; //用于存放该线路中的所有站点信息
3.1.3 Graph
通过自定义图结构,方便后续BFS的计算。
private Set<Station> allStations = new HashSet<Station>(); //用于存放所有站点信息
private Map<Station, LinkedList<Station>> adj = new HashMap<Station, LinkedList<Station>>(); //邻接表,用于存放每个站点相邻站点的信息
3.2 Control模块
3.2.1 InformationProcessing
该类主要实现了从文件中读取地铁信息,并以此生成邻接表结构的图。由于存在换乘站点,因此多条线路中可能存在同一站点,为了便于后续邻接表的生成,只能允许存在一个相同站点信息,需要判断读入的站点信息是否已经存在。因此通过isExisted方法来判断是否已存在相同站点信息。同时还需读入每个站点相邻站点的信息,因此需要第二次读入操作。
public ArrayList<Line> inputInformation(String filePath) throws IOException{
File file =new File(filePath);
ArrayList<Line> lines=this.lines;
ArrayList<Station> allStations=this.allStations;
try {
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file),
"UTF-8"));
String lineTxt=null;
while ((lineTxt=br.readLine())!=null){
String[] txt = lineTxt.trim().split(" ");
Line line=new Line();
line.setLineName(txt[0]);
ArrayList<Station> stations=new ArrayList<Station>();
for (int i=1;i<txt.length;i++){
Station station=isExisted(txt[i],allStations);
if (station==null){
station=new Station();
station.setStationName(txt[i]);
allStations.add(station);
}else{
station.setChangingStation(true);
}
stations.add(station);
}
line.setStations(stations);
lines.add(line);
}
br = new BufferedReader(new InputStreamReader(new FileInputStream(file),
"UTF-8"));
lineTxt=null;
while ((lineTxt=br.readLine())!=null){
String[] txt = lineTxt.trim().split(" ");
for (int i=1;i<txt.length;i++){
Station station=new Station();
if (i>1&&i<txt.length-1){
station=this.Search(txt[i], allStations);
if (station!=null){
Station newStation=new Station();
newStation=this.Search(txt[i-1], allStations);
if (newStation!=null){
station.addConnection(newStation);
}
newStation=this.Search(txt[i+1], allStations);
if (newStation!=null){
station.addConnection(newStation);
}
}
}else if (i==1){
station=this.Search(txt[i], allStations);
if (station!=null){
Station newStation=new Station();
newStation=this.Search(txt[i+1], allStations);
if (newStation!=null){
station.addConnection(newStation);
}
}
}else if (i==txt.length-1){
station=this.Search(txt[i], allStations);
if (station!=null){
Station newStation=new Station();
newStation=this.Search(txt[i-1], allStations);
if (newStation!=null){
station.addConnection(newStation);
}
}
}
}
}
this.allStations=allStations;
} catch (UnsupportedEncodingException | FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return lines;
}
public Station isExisted(String stationName, ArrayList<Station> allStations){
for (int i=0;i<allStations.size();i++){
if (stationName.equals(allStations.get(i).getStationName())){
return allStations.get(i);
}
}
return null;
}
public Station Search(String stationName, ArrayList<Station> allStations) //用于搜索站点信息
public void addConnection(String stationName,Station station) //用于添加站点邻接信息
public Map<String, Line> generateMap(ArrayList<Line> lines) //用于创建map映射,方便读取线路信息
public void showLine(String lineName,Map<String, Line> lineMap,String filepath) throws IOException //用于生成某条线路的所有站点,并进行输出
public Graph generateGraph(ArrayList<Station> allStations) //用于将现有数据生成邻接表
3.2.2 ShortestPath
该类主要用来进行最短路径的计算,所用的算法为BFS广度优先搜索,所用的数据结构为邻接表。
为了实现BFS算法,由于Map有唯一key与value相对应,于是我用Map来初始化了visited,用Map来存放pre。
//核心算法 BFS
public void bfs(Station s){
Queue<Station> queue=new LinkedList<>();
queue.add(s);
pre.put(s, s);
visited.put(s, true);
while(!queue.isEmpty()){
Station v=queue.remove();
for (Station w: g.getAdj().get(v)) {
if (!visited.get(w)) {
queue.add(w);
visited.put(w, true);
pre.put(w, v);
}
}
}
}
由于存在不同的站点类型,有普通站点,也有换乘站点,换乘站点存在于多条线路中。为了确定当前处于哪条线路中,对不同的情况需要进行不同的分析。当前站点为普通站点时,直接获得当前线路。但当前站点为换乘站点时,又需要分为两种情况进行讨论。第一种,当前为换乘站点,下一站为普通站点时,直接获取下一站站点的线路作为当前线路。第二种,连续两个都为换乘站点时,取两个站点同时都有的线路作为当前线路。
//最麻烦的输出代码
public void showShortestPath(ArrayList<Station> path, String filepath) throws IOException {
File f=new File(filepath);
FileOutputStream fos1=new FileOutputStream(f);
OutputStreamWriter dos1=new OutputStreamWriter(fos1);
System.out.println("总站点数:"+path.size()+"站");
dos1.write("总站点数:"+path.size()+"站"+"\n");
System.out.println();
System.out.println("具体线路:");
dos1.write("具体线路:"+"\n");
Station start=path.get(0);
Station second=path.get(1);
String lineName="";
if (start.getChangingStation()==false&&second.getChangingStation()==false){
lineName=this.inLine(start).get(0).getLineName();
}
else if (start.getChangingStation()==true&&second.getChangingStation()==false){
lineName=this.inLine(second).get(0).getLineName();
}
else if(start.getChangingStation()==true&&second.getChangingStation()==true){
lineName=this.isInTheSameLine(start,second);
}
else{
lineName=this.inLine(start).get(0).getLineName();
}
System.out.println("--->乘坐:"+lineName);
dos1.write("--->乘坐:"+lineName+"\n");
System.out.println(start.getStationName());
dos1.write(start.getStationName()+"\n");
for (int i=1;i<path.size();i++){
Station nowStation=path.get(i);
String curLineName="";
if (i<path.size()-1){
Station nextStation=path.get(i+1);
if (nowStation.getChangingStation()==false&&nextStation.getChangingStation()==false){
curLineName=this.inLine(nowStation).get(0).getLineName();
}
else if (nowStation.getChangingStation()==true&&nextStation.getChangingStation()==false){
curLineName=this.inLine(nextStation).get(0).getLineName();
}
else if(nowStation.getChangingStation()==true&&nextStation.getChangingStation()==true){
curLineName=this.isInTheSameLine(nowStation,nextStation);
}
else{
curLineName=this.inLine(nowStation).get(0).getLineName();
}
System.out.println(nowStation.getStationName());
dos1.write(nowStation.getStationName()+"\n");
if (!lineName.equals(curLineName)){
lineName=curLineName;
System.out.println("--->换乘:"+lineName);
dos1.write("--->换乘:"+lineName+"\n");
}
}
else{
Station preStation=path.get(i-1);
if (preStation.getChangingStation()==true&&nowStation.getChangingStation()==false){
if (!this.inLine(nowStation).get(0).getLineName().equals(lineName)){
System.out.println("--->换乘:"+this.inLine(nowStation).get(0).getLineName());
dos1.write("--->换乘:"+this.inLine(nowStation).get(0).getLineName()+"\n");
}
}
System.out.println(nowStation.getStationName());
dos1.write(nowStation.getStationName()+"\n");
}
}
dos1.close();
}
public String isInTheSameLine(Station s1,Station s2) //返回两个换乘站点的相同线路名
public ArrayList<Line> inLine(Station station) //判断当前站点所在的线路,换乘站点存在多个线路
public Iterable<Station> path(Station endStation) //用于得到路径信息
3.1 Main模块
3.1.1 Subway
该主函数用于实现不同参数输入,实现不同的功能。利用switch case结构来对不同的参数进行不同功能的实现。
public static void main(String[] args) throws Exception {
switch (args[0]){
case "-map":
//-map subway.txt
if(args.length==2){
InformationProcessing ii=new InformationProcessing();
ii.inputInformation(args[1]);
System.out.println("成功读取subway.txt文件");
ArrayList<Line> lines=new ArrayList<Line>();
lines=ii.inputInformation(args[1]);
for (int i=0;i<lines.size();i++){
System.out.println(lines.get(i).getLineName());
for (int j=0;j<lines.get(i).getStations().size();j++){
System.out.print(lines.get(i).getStations().get(j).getStationName()+" ");
}
System.out.println();
}
}else{
System.out.println("验证参数格式!");
}
break;
case "-a":
//-a 1号线 -map subway.txt -o station.txt
if(args.length==6){
InformationProcessing ii=new InformationProcessing();
ArrayList<Line> lines=new ArrayList<Line>();
lines=ii.inputInformation(args[3]);
if (ii.isLineExisted(args[1], ii.generateMap(ii.getLines()))==true){
ii.showLine(args[1],ii.generateMap(ii.getLines()),args[5]);
}
else {
throw new Exception("线路不存在");
}
System.out.println("已将结果写入station.txt文件");
}else{
System.out.println("验证参数格式!");
}
break;
case "-b":
//-b 洪湖里 复兴路 -map subway.txt -o routine.txt
if(args.length==7){
ArrayList<Line> lines=new ArrayList<Line>();
InformationProcessing ii=new InformationProcessing();
lines=ii.inputInformation(args[4]);
Graph g=ii.generateGraph(ii.getAllStations());
if (args[1].equals(args[2])){
throw new Exception("请输入两个不同站点");
}
if (ii.Search(args[1], ii.getAllStations())==null){
throw new Exception("站点1不存在,请重新输入");
}
if (ii.Search(args[2], ii.getAllStations())==null){
throw new Exception("站点2不存在,请重新输入");
}
ShortestPath sp=new ShortestPath(ii.Search(args[1], ii.getAllStations()),g,lines);
ArrayList<Station> path=new ArrayList<>();
path=(ArrayList<Station>)sp.path(ii.Search(args[2],ii.getAllStations()));
sp.showShortestPath(path,args[6]);
System.out.println("已将结果写入routine. txt文件");
}else{
System.out.println("验证参数格式!");
}
break;
default:
throw new Exception("参数输入错误");
}
}
4.程序演示
1..利用 -map
参数来获得对应的自定义地铁文件(命名为 subway.txt)
java subway -map subway.txt
文件格式
1号线 苹果园 古城 八角游乐园 八宝山 玉泉路 五棵松 万寿路 公主坟 军事博物馆 木樨地 南礼士路 复兴门 西单 天安门西 天安门东 王府井 东单 建国门 永安里 国贸 大望路 四惠 四惠东
2号线 积水潭 鼓楼大街 安定门 雍和宫 东直门 东四十条 朝阳门 建国门 北京站 崇文门 前门 和平门 宣武门 长椿街 复兴门 阜成门 车公庄 西直门 积水潭
运行结果:
2.支持一个新的命令行参数 -a
进行地铁线路查询,并通过命令参数-o
进行输出
java subway -a 2号线 -map subway.txt -o station.txt
运行结果:
3.利用参数-b
查询两个站点之间最短(经过的站点数最少)路线,并输出
subway.exe -b 动物园 和平里北街 -map subway.txt -o routine.txt
3.1 两个站点均为普通站点
运行结果:
3.2 起点为转站站点,终点为普通站点
运行结果:
3.3 起点为普通站点,终点为转站站点
运行结果:
3.4 起点和终点均为转站站点
运行结果:
4.健壮性演示
参数错误:
站点不存在:
线路不存在:
输入相同站点:
5.总结
通过这次大作业,我明白了一个项目不仅仅是只有编写程序这么简单,重要的是开始的规划,所需知识的学习,实践代码,完善代码,查漏补缺,对项目进行各种各样的测试,这整个过程都是十分重要的。不仅如此,通过这次大作业,我也得以复习了大二数据结构课程所学习的图算法,对邻接表这一数据结构也有了更深的认识,对广度优先搜索算法也变得更加熟练,同时,由于本次大作业是使用Java作为主要语言,因此也锻炼了Java编程能力,总之受益匪浅。但这次实验中依然存在不足,一些代码过分冗余、复杂,一些算法也不太熟练。希望通过这次实验能弥补自身的不足,争取在期末大作业中更进一步。