北京地铁两站点间最短路径实现
主要功能
提供一副如下所示的地铁线路图

地铁线路文本存储如下图所示

计算指定两站之间最短(最少经过站数)乘车路线;输出指定地铁线路的所有站点。以北京地铁为例,地铁线路信息保存在subwaydata.txt中,格式如下:
地铁线路总数
线路名1 站名1 站名2 站名3 ...
线路名2 站名1 站名2 站名3 ...
线路名3 站名1 站名2 站名3 ......
需求分析
读入给定的地铁线路文本,获取地铁线路信息。处理输入的命令,若两站正确存在线路信息中,程序需要根据命令给出两站间的最短路径,并输出所需乘坐的地铁线路以及地铁经过的站点;若不存在这两个站点,则给出错误提示。
实现语言
JAVA
实现算法
Dijkstra
类职责划分
为了清晰的表明各类用途,建立了两个package,分别为model和util,在model包中存放Station类和Result类两个模型,在util包中存放SubwayData类和Util类两个主要的程序实现功能类。此外还有一个Main类,作为程序的入口。
1、Station类
存储各个地铁站的信息,主要信息有站名、站点线路、与站点相连的地铁站。
2、Result类
存储程序运行后的结果,主要信息有起点站、终点站、经过站数、本站的上一站、本站线路、上一站到本站是否有换乘。
3、SubwayData类
在这个类中存放了地铁的图结构。本类实现了读入地铁线路数据并保存、处理各个车站换乘等信息的功能。
4、Util类
在这个类实现程序核心功能————利用dijkstra算法寻找最短路径。本类实现的其他功能还有生成乘车路径、获取地铁线路所有站点。
5、Main类
这个类是程序的入口,实现了程序的简单操作、提示功能,将以上四个类功能融合实现程序运行。
核心代码
完整代码已上传到GitHub上:
https://github.com/Bazingaali/SE_Subway/
1、Station类
public class Station{
private String name; //站名
private ArrayList<String> subway = new ArrayList<String>(); //站点线路
private ArrayList<Station> connect = new ArrayList<Station>(); //与站点相连的地铁站
public Station() {
super();
// TODO Auto-generated constructor stub
}
public Station(String name, String subway) {
this.name = name;
this.subway.add(subway);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ArrayList<String> getSubway() {
return subway;
}
public void setSubway(ArrayList<String> subway) {
this.subway = subway;
}
public ArrayList<Station> getConnect() {
return connect;
}
public void setConnect(ArrayList<Station> connect) {
this.connect = connect;
}
}
2、Result类
public class Result {
private Station begin; //起始站
private Station end; //终点站
private int step; //经过站数
private Station stationbefore; //本站的上一站
private String linenumber; //本站线路
private int changeline; //上一站到本站是否有换乘,0为无换乘,1为需换乘
public Station getBegin() {
return begin;
}
public void setBegin(Station begin) {
this.begin = begin;
}
public Station getEnd() {
return end;
}
public void setEnd(Station end) {
this.end = end;
}
public int getStep() {
return step;
}
public void setStep(int step) {
this.step = step;
}
public Station getStationbefore() {
return stationbefore;
}
public void setStationbefore(Station stationbefore) {
this.stationbefore = stationbefore;
}
public String getLinenumber() {
return linenumber;
}
public void setLinenumber(String linenumber) {
this.linenumber = linenumber;
}
public int getChangeline() {
return changeline;
}
public void setChangeline(int changeline) {
this.changeline = changeline;
}
}
3、SubwayData类
public class SubwayData {
public static LinkedHashSet<List<Station>> lineset = new LinkedHashSet<List<Station>>(); //存储所有线路
public SubwayData(String filename) throws IOException {
File file = new File(filename);
InputStreamReader reader = new InputStreamReader( new FileInputStream(file),"UTF-8"); //输入字符格式为UTF-8,防止乱码
BufferedReader br = new BufferedReader(reader);
String readtxt = "";
readtxt = br.readLine();//在文档第一行表示共有几条地铁线路,获取地铁线路数量,为后面的循环做准备
int linenumber = Integer.parseInt(readtxt);//linenumber中存放地铁线路数
for(int i = 0 ; i < linenumber ; i++) { //往lineSet集合中添加各条地铁线路信息
List<Station> line = new ArrayList<Station>();//存储各条地铁线信息
readtxt = br.readLine();
String[] lineinformation = readtxt.split(" ");
String name = lineinformation[0];
for(int j = 1 ; j < lineinformation.length ; j++) { //往line中添加各个站的信息
int flag = 0;//标识符,flag=1时处理结束,跳出循环
for(List<Station> l:lineset) { //处理换乘站信息
for(int k = 0 ; k < l.size() ; k++) {
if(l.get(k).getName().equals(lineinformation[j])) {
ArrayList<String> line1 = l.get(k).getSubway();
line1.add(name);
l.get(k).setSubway(line1);
line.add(l.get(k));
flag=1;
break;
}
}
if(flag==1)
break;
}
if(j==lineinformation.length-1&&lineinformation[j].equals(lineinformation[1])) { //处理环线信息
line.get(0).getConnect().add(line.get(line.size()-1));//环线的第1站与最后一站相同,将首尾连接起来
line.get(line.size()-1).getConnect().add(line.get(0));
flag=1;
}
if(flag==0)
line.add(new Station(lineinformation[j],name));
}
for(int j = 0;j < line.size() ;j++) { //处理每一个车站的相邻车站
ArrayList<Station> connectStations=line.get(j).getConnect();
if(j==0) {//处理首站的相邻车站
connectStations.add(line.get(j+1));
line.get(j).setConnect(connectStations);
}
else if(j==line.size()-1) {//处理终点站的相邻车站
connectStations.add(line.get(j-1));
line.get(j).setConnect(connectStations);
}
else {//处理其他车站的相邻车站
connectStations.add(line.get(j+1));
connectStations.add(line.get(j-1));
line.get(j).setConnect(connectStations);
}
}
lineset.add(line);
}
br.close();
}
}
4、Util类
public class Util {
private static ArrayList<Station> analysised = new ArrayList<>(); //已经分析过的站点
private static HashMap<Station, Result> resultmap = new HashMap<>(); //结果集
private static List<String> getsameline(List<String> list1,List<String> list2) {//得到list1和list2中相同的线路
List<String> sameline=new ArrayList<String>();
for(String l1:list1) {
for(String l2:list2) {
if(l1.equals(l2))
sameline.add(l1);
}
}
return sameline;
}
private static Station getnextstation() {//得到下一个分析站点
int min=66666;//存储以本站出发各个节点到到达站的最小距离。首先将min设置为一个较大值,后续比较逐渐缩小
Station nextstation = null;//下一个节点先置空节点
Set<Station> stations = resultmap.keySet();
for (Station station : stations) {
if (analysised.contains(station)) {//首先判断当前节点是否已经被分析过,若分析过,则跳过
continue;
}
Result result1 = resultmap.get(station);//存储结果
if (result1.getStep() < min) {
min = result1.getStep();
nextstation = result1.getEnd();
}
}
return nextstation;
}
public static Result dijkstrashortest(Station begin, Station end) { //dijkstra算法计算两地铁站中最短路径
for(List<Station> list:SubwayData.lineset) {
for(int k = 0 ; k < list.size() ; k++) {
Result result = new Result();//初始化结果集
result.setBegin(begin);
result.setEnd(list.get(k));
result.setStep(666666);
result.setChangeline(0);
resultmap.put(list.get(k), result);
}
}
for(Station s:begin.getConnect()) { //对结果集逐个赋值
resultmap.get(s).setStep(1);
resultmap.get(s).setStationbefore(begin);
List<String> samelines = getsameline(begin.getSubway(),s.getSubway());
resultmap.get(s).setLinenumber(samelines.get(0));
}
resultmap.get(begin).setStep(0);
analysised.add(begin);
Station nextstation = getnextstation(); //对下一个站点进行分析
while(nextstation!=null) { //计算最短路径
for(Station s:nextstation.getConnect()) {
if(resultmap.get(nextstation).getStep()+1<resultmap.get(s).getStep()) { //更新最短路径
resultmap.get(s).setStep(resultmap.get(nextstation).getStep()+1);
resultmap.get(s).setStationbefore(nextstation);
List<String> samelines = getsameline(nextstation.getSubway(),s.getSubway());
if(!samelines.contains(resultmap.get(nextstation).getLinenumber())) { //判断是否换乘
resultmap.get(s).setLinenumber(samelines.get(0));
resultmap.get(s).setChangeline(1);
}
else {
resultmap.get(s).setLinenumber(resultmap.get(nextstation).getLinenumber());
}
}
}
analysised.add(nextstation); //在已分析节点数组中加上此站
nextstation = getnextstation();//得到下一站,继续循环分析
}
return resultmap.get(end);
}
public static List<Station> getLineStation(String linename){ //获取地铁线路的所有站点
int flag = 0;//标识符,flag=1时处理结束,跳出循环
for (List<Station> list:SubwayData.lineset) {
flag = 0;
for(Station station : list) {
if(!station.getSubway().contains(linename))
flag = 1;
}
if(flag == 0)
return list;
}
return null;
}
public static List<String> getPath(Result r){//生成乘车路线
List<String> path=new ArrayList<String>();//存储乘车路线
Stack<Station> stationtemp=new Stack<Station>();//利用栈存储各站信息
Station s=r.getStationbefore();
while(!s.equals(r.getBegin())) {
stationtemp.push(s);
s=resultmap.get(s).getStationbefore();
}
path.add("!!首先在" + r.getBegin().getName() + "乘坐" + resultmap.get(stationtemp.peek()).getLinenumber());
path.add(r.getBegin().getName());
while(!stationtemp.empty()) {
if(resultmap.get(stationtemp.peek()).getChangeline()==1) {
path.add("!!在本站换乘"+resultmap.get(stationtemp.peek()).getLinenumber());
path.add(stationtemp.pop().getName());
}
else
path.add(stationtemp.pop().getName());
}
if(r.getChangeline()==1) {
path.add("!!在本站换乘"+r.getLinenumber());
path.add(r.getEnd().getName());
}
else
path.add(r.getEnd().getName());
return path;
}
}
5、Main类
public class Main {
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
new SubwayData("subwaydata.txt");
System.out.print("请选择查询内容:1、查询指定地铁线路;2、查询两站间地铁路径 ");
int nn = Integer.parseInt(scanner.next());
if(nn==1) {
System.out.print("请输入需要查询的地铁线路 ");
String searchline = scanner.next();
List<Station> stations = Util.getLineStation(searchline);
String put = "";
if(stations==null)
put = "该地铁线路不存在";
else {
for(int i=0;i<stations.size();i++) {
if(i==stations.size()-1&&stations.get(i).getConnect().contains(stations.get(0)))
put = put + stations.get(i).getName() + " !!" + searchline + "为环线" + "!!";
else
put = put + stations.get(i).getName()+" ";
}
}
printout(put , "result.txt");
System.out.print(put);
}
else if(nn==2) {
System.out.print("请输入起始站与到达站,两站中以空格分隔 ");
String station1 = scanner.next();
String station2 = scanner.next();
Station begin = null;
Station end = null;
for(List<Station> list : SubwayData.lineset){
for(int k=0 ;k<list.size() ;k++) {
if(list.get(k).getName().equals(station1)) {
begin = list.get(k);
}
if(list.get(k).getName().equals(station2)) {
end = list.get(k);
}
}
}
String put = "";
if(begin==null)
put = "出发站不存在";
else if (end==null)
put = "到达站不存在";
else {
Result result = Util.dijkstrashortest(begin , end);
List<String> path = Util.getPath(result);
put = "从" + station1 + "到" + station2 + "最少经过"+ (result.getStep() + 1) + "站,以下为详细乘车方案\n";
for(String s:path)
put = put + s + "\n";
}
printout(put,"result.txt");
System.out.print(put);
}
return;
}
public static void printout(String content,String path) throws IOException {
File file = new File(path);
if(!file.exists()){
file.createNewFile();
}
FileOutputStream outputStream = new FileOutputStream(file);
byte[] bytes = content.getBytes("UTF-8");
outputStream.write(bytes);
outputStream.close();
}
}
测试用例
源程序运行时,会将输出结果输出至主目录下的result.txt文档中,为方便测试结果展示,以下测试结果均在控制台中输出展示。
1、查询存在的地铁线路,输出该条地铁线路全线站点

若查询地铁线路为环线,则在输出完站点后提示该线路为环线

2、查询不存在的地铁线路,则会提示错误

3、查询两站间的最短路径(若两站之间有多条相同步长的最短路径,则为用户展示换成次数最少的乘坐方式
3.1、两站在同一条线路上(无需换乘)

3.2、两站在不同线路上(需要换乘)

3.3、出发站点不存在

3.4、到达站点不存在

总结
经过本项目的磨炼,我收获了很多,也学会了很多有用的东西,以下是我对本次作业详细的总结。
1、对于一个较复杂的软件的开发步骤有了一定的了解
2、编程能力得到了一定的提升,对于java有了更深的理解,也发现自己还有很多不足,需要进行更深入的学习
3、第一次写博客,也是一种锻炼,在写博客的过程中也学到了很多

浙公网安备 33010602011771号