仓库代码:https://github.com/arthurcao99/SubwayHelper
地铁出行路线规划设计:https://www.cnblogs.com/caolx/p/11558996.html
该项目分为Control、Main、Model这三个模块。Control模块用于存放文件读取和计算路径代码,Main模块用于存放主函数,Model用于存放各个实体类,如Station类、Line类等。
由于开始规划这个项目时,未仔细阅读任务要求,错误认为该项目需要利用Dijkstra最短路径算法来求出两个站点之间的最短距离,后发现该程序的要求仅仅是最少站点,对换乘次数并未作具体要求,因此采用了BFS广度优先搜索算法来计算两个站点之间最短路径。
private String stationName; //用于存放站点名称
private LinkedList<Station> connectedStation; //用于存放该站点相邻站点,类似于邻接表结构
private Boolean isChangingStation=false; //用于判断该站点是否为换乘站点
private String lineName; //用于存放线路名称
private ArrayList<Station> stations; //用于存放该线路中的所有站点信息
通过自定义图结构,方便后续BFS的计算。
private Set<Station> allStations = new HashSet<Station>(); //用于存放所有站点信息
private Map<Station, LinkedList<Station>> adj = new HashMap<Station, LinkedList<Station>>(); //邻接表,用于存放每个站点相邻站点的信息
该类主要实现了从文件中读取地铁信息,并以此生成邻接表结构的图。由于存在换乘站点,因此多条线路中可能存在同一站点,为了便于后续邻接表的生成,只能允许存在一个相同站点信息,需要判断读入的站点信息是否已经存在。因此通过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) //用于将现有数据生成邻接表
该类主要用来进行最短路径的计算,所用的算法为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) //用于得到路径信息
该主函数用于实现不同参数输入,实现不同的功能。利用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("参数输入错误");
}
}
1..利用 -map
参数来获得对应的自定义地铁文件(命名为 subway.txt)
java subway -map subway.txt
运行结果:
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 起点为普通站点,终点为转站站点
运行结果:
4.健壮性演示
参数错误:
站点不存在:
线路不存在:
输入相同站点:
通过这次大作业,我明白了一个项目不仅仅是只有编写程序这么简单,重要的是开始的规划,所需知识的学习,实践代码,完善代码,查漏补缺,对项目进行各种各样的测试,这整个过程都是十分重要的。不仅如此,通过这次大作业,我也得以复习了大二数据结构课程所学习的图算法,对邻接表这一数据结构也有了更深的认识,对广度优先搜索算法也变得更加熟练,同时,由于本次大作业是使用Java作为主要语言,因此也锻炼了Java编程能力,总之受益匪浅。但这次实验中依然存在不足,一些代码过分冗余、复杂,一些算法也不太熟练。希望通过这次实验能弥补自身的不足,争取在期末大作业中更进一步。
原文:https://www.cnblogs.com/caolx/p/11669479.html