Github https://github.com/mmmmm962/BeiJingSubway1
一、任务:
实现一个帮助进行地铁出行路线规划的命令行程序。
需求1:
程序启动时需要通过读取 -map 参数来获得对应的自定义地铁文件(命名为 subway.txt),从而得到地铁线路图的信息。
java subway -map subway.txt
需求2:
查询地铁线路,在应用程序需要支持一个新的命令行参数 -a,它指定了用户希望查询的地铁线路。程序就需要能够从线路的起始站点开始,依次输出该地铁线经过的所有站点,直到终点站。输出的文件使用 -o 命令行参数来指定。
java subway -a 1号线 -map subway.txt
需求3:
获取两个站点之间的最短路径,在命令行中以 -b 参数加两个地铁站点名称分别作为出发地与目的地,比如用户希望知道 洪湖里 到复兴路 之间的最短路线是怎样的,可以使用如下命令
java subway -b 洪湖里 复兴路 -map subway.txt -o routine.txt
程序将计算从出发到目的站点之间的最短(经过的站点数最少)路线,并输出经过的站点的个数和路径(包括出发与目的站点)。注意,如果需要换乘,请在换乘站的下一行输出换乘的线路。上面样例的输出就会存入 routine.txt 文件中,文件内容如下:
3 洪湖里 西站 6号线 复兴路
二、文件的存储:
将地铁线路信息存储为subway.txt文件,放置于项目的bin目录下(为了方便起见,将纯中文名的地铁线路也标记为特定的一个数字)
*1-苹果园,古城,八角游乐园,八宝山,玉泉路,五棵松,万寿路,公主坟,军事博物馆,木樨地,南礼士路,复兴门,西单,天安门西,天安门东,王府井,东单,建国门,永安里,国贸,大望路,四惠,四惠东 *2-西直门,积水潭,鼓楼大街,安定门,雍和宫,东直门,东四十条,朝阳门,建国门,北京站,崇文门,前门,和平门,宣武门,长椿街,复兴门,阜成门,车公庄 *4-安河桥北,北宫门,西苑,圆明园,北京大学东门,中关村,海淀黄庄,人民大学,魏公村,国家图书馆,动物园,西直门,新街口,平安里,西四,灵境胡同,西单,宣武门,菜市口,陶然亭,北京南站,马家堡,角门西,公益西桥 *5-宋家庄,刘家窑,蒲黄榆,天坛东门,磁器口,崇文门,东单,灯市口,东四,张自忠路,北新桥,雍和宫,和平里北街,和平西桥,惠新西街南口,惠新西街北口,大屯路东,北苑路北,立水桥南,立水桥,天通苑南,天通苑,天通苑北
三、放置的路径:
北京地铁线路规划项目,我一共编写了Dijkstra、Line、Path、Station、subway五个JAVA文件

包含地铁线路信息的subway.txt文件放置于bin目录下,查看地铁线路的站点信息会输出到station.txt文件中,最短路径的信息会输出到routine.txt文件中,这两个文件都会生成于bin目录下

四、存储的结构:
站点信息
Station private String stationName; // 站点名称 private String lineName; // 线路名称 private List<Station> linkStation = new ArrayList<>(); // 相邻站点
相应的get、set方法
public String getStationName() {
return stationName;
}
public void setStationName(String stationName) {
this.stationName = stationName;
}
public String getLineName() {
return lineName;
}
public void setLineName(String lineName) {
this.lineName = lineName;
}
public List<Station> getLinkStation() {
return linkStation;
}
public void setLinkStation(List<Station> linkStation) {
this.linkStation = linkStation;
}
public Station(String stationName, String lineName) {
this.stationName = stationName;
this.lineName = lineName;
}
public Station(String stationName) {
this.stationName = stationName;
}
public boolean equals(Object obj) { // 判断传入对象的属性
if (this == obj) {
return true;
} else if (obj instanceof Station) {
Station station = (Station) obj;
if (station.getStationName().equals(this.getStationName())) {
return true;
} else {
return false;
}
} else {
return false;
}
}
路径信息(get、set方法略)
path private Station start; // 开始站点 private Station end; // 结束站点 private Double distance = 0.0; // 站点间的距离 private List<Station> passStation = new ArrayList<>(); //经过的站点
线路信息
line public static HashMap<String, List<Station>> lineData; // 地铁线路数据 public static LinkedHashSet<List<Station>> lineSet = new LinkedHashSet<>();// 地铁线路集合
五、基本的代码及方法:
命令行读取相关文件以及参数的设置
switch (args[0]) { // 从命令行读取参数
case "-map":
if (args.length == 2) {
Line.readFile = System.getProperty("user.dir") + File.separator + "\\" + args[1];
// 读取地铁信息
Line.readFile();
System.out.println("成功读取地铁信息");
} else {
System.out.println("输入不正确!");
break;
}
break;
case "-a":
if (args.length == 6) {
Line.readFile = System.getProperty("user.dir") + File.separator + "\\" + args[3];
Line.writeFile = System.getProperty("user.dir") + File.separator + "\\" + args[5];
Line.readFile();
Line.writeLine(args[1]);
System.out.println("该线路的信息为:");
Line line = new Line();
line.readLine();
} else {
System.out.println("输入不正确!");
break;
}
break;
case "-b":
if (args.length == 7) {
Line.readFile = System.getProperty("user.dir") + File.separator + "\\" + args[4];
Line.writeFile = System.getProperty("user.dir") + File.separator + "\\" + args[6];
Line.readFile();
Path path = Dijkstra.calculate(new Station(args[1]), new Station(args[2]));
Line.writePassStation(path);
System.out.println("最短路径信息为:");
Line line = new Line();
path.readShort();
} else {
System.out.println("输入不正确!");
break;
}
break;
default:
System.out.println("输入不正确!");
}
读取subway.txt文件的方法
public static void readFile() { //读取地铁线路图信息
File file = new File(readFile);
BufferedReader reader = null;
try {
InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream(file), "UTF-8");
reader = new BufferedReader(inputStreamReader);
String line = null;
String lineName = "1";
while ((line = reader.readLine()) != null) {
if (line.trim().startsWith("*")) {
String[] lineInfo = line.substring(1).split("-");
lineSet.add(getLine(lineInfo[1].trim(), lineInfo[0].trim()));
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
线路、站点相关的方法
public static void createline() { //存储地铁线路
lineData = new HashMap<>();
for (List<Station> stations : lineSet) {
lineData.put(stations.get(1).getLineName(), stations);
}
}
public static String getLineNameByStation(Station station) { //通过站点获取线路名称
createline();
String startname = station.getStationName();
for (Map.Entry<String, List<Station>> entry : lineData.entrySet()) {
List<Station> stations = entry.getValue();
for (Station station1 : stations) {
if (station1.getStationName().equals(startname)) {
return entry.getKey();
}
}
}
return "";
}
public static ArrayList<Station> getLine(String lineName1, String lineName2) { // 读取线路信息
ArrayList<Station> line = new ArrayList<Station>();
String[] lineArr = lineName1.split(",");
for (String s : lineArr) {
line.add(new Station(s, lineName2));
}
return line;
}
public static String writeLine(String lineName) throws UnsupportedEncodingException, FileNotFoundException { // 提取相应线路的信息
createline();
lineName = lineName.substring(0, 1);
List<Station> lineInfo = lineData.get(lineName);
String line = lineInfo.stream().map(x -> x.getStationName()).collect(Collectors.joining(","));
try {
Files.write(Paths.get(writeFile), line.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
return line;
}
public static void writePassStation(Path path) { //输出最短路径
FileWriter f = null;
BufferedWriter b = null;
try {
f = new FileWriter(new File(writeFile), true);
b = new BufferedWriter(f);
b.write((path.getPassStation().size() + 1) + "\t\n"); // 写入站台数
b.write(path.getStart().getStationName() + "\t\n"); // 写入站台名称
String startLineName = getLineNameByStation(path.getStart());// 获取地铁线路
String line = startLineName; // 默认转乘地铁线路与当前一致
for (Station station : path.getPassStation()) {
if (!line.equals(station.getLineName())) {
b.write(station.getLineName() + "号线" + "\t\n"); // 写入转乘线路
b.write(station.getStationName() + "\t\n");
line = station.getLineName();
} else {
b.write(station.getStationName() + "\t\n");
}
}
b.close();
f.close();
} catch (IOException e) {
e.printStackTrace();
}
}
六、核心Dijkstra算法:
使用dijkstra算法求解两个地铁站之间的最短路径问题,默认将相邻站点之间的距离设置为1;
存储的结构
private static HashMap<Station, Path> resultMap = new HashMap<>(); //结果集
private static List<Station> visitedStation = new ArrayList<>(); //遍历过的站点
获取相邻结点
public static List<Station> getLinkStation(Station station) { // 获取所有相邻点
List<Station> linkedStaion = new ArrayList<Station>();
for (List<Station> line : Line.lineSet) {
for (int i = 0; i < line.size(); i++) {
if (station.equals(line.get(i))) {
if (i == 0) {
linkedStaion.add(line.get(i + 1)); //此站点为起始站点时
} else if (i == (line.size() - 1)) {
linkedStaion.add(line.get(i - 1)); //此站点是最后一个站点时
} else {
linkedStaion.add(line.get(i + 1)); //位于其余位置
linkedStaion.add(line.get(i - 1));
}
}
}
}
return linkedStaion;
}
计算最短距离
private static Station getNextStation() { //获得下一个需要分析的点
Double min = 999999.0;
Station s = null;
Set<Station> stations = resultMap.keySet();
for (Station station : stations) {
if (visitedStation.contains(station)) {
continue;
}
Path result = resultMap.get(station);
if (result.getDistance() < min) { //比较获得最短距离
min = result.getDistance();
s = result.getEnd();
}
}
return s;
}
循环遍历得出结果
public static Path calculate(Station start, Station end) { //循环遍历获得结果
if (!visitedStation.contains(start)) { //
visitedStation.add(start);
}
// 如果开始站点等于终止站点,则设置result,设置距离和station。将开始结点标记已遍历
if (resultMap.isEmpty()) {
List<Station> linkStation = getLinkStation(start);
for (Station station : linkStation) { // 将相邻站点加入结果集
Path path = new Path();
path.setStart(start);
path.setEnd(station);
path.setDistance(1.0); // 默认站点间距离都为1
path.getPassStation().add(station);
resultMap.put(station, path);
}
}
Station parent = getNextStation();
if (parent == null) { // 所有站点都已经遍历完成
Path path = new Path();
path.setDistance(0.0);
path.setStart(start);
path.setEnd(end);
return resultMap.put(end, path);
}
// 如果得到的最佳邻点与目标点相同,则直接返回最佳邻点对应的result对象。
if (parent.equals(end)) {
return resultMap.get(parent);
}
List<Station> childLinkStation = getLinkStation(parent);
for (Station child : childLinkStation) {
if (visitedStation.contains(child)) {
continue;
}
Double distance = 0.0;
if (parent.getStationName().equals(child.getStationName())) {
distance = 0.0;
}
Double parentDistance = resultMap.get(parent).getDistance();
distance = parentDistance + 1.0;
List<Station> parentPassStation = resultMap.get(parent).getPassStation();
Path childResult = resultMap.get(child);
if (childResult != null) { // 含有最佳相邻点
if (childResult.getDistance() > distance) {
childResult.setDistance(distance);
childResult.getPassStation().clear();
childResult.getPassStation().addAll(parentPassStation);
childResult.getPassStation().add(child);
}
} else {
childResult = new Path(); // 没有最佳相邻点
childResult.setDistance(distance);
childResult.setStart(start);
childResult.setEnd(child);
childResult.getPassStation().addAll(parentPassStation);
childResult.getPassStation().add(child);
}
resultMap.put(child, childResult);
}
visitedStation.add(parent);
return calculate(start, end);
}
七、测试:
正常情况测试:
需求1:读取地铁线路的信息

需求2:查询相应线路的站点信息

并且每次会在station.txt文件中有相应的输出

需求三:查询两个站点之间的最短路径
苹果园-四惠之间最短路径相差21站;

在routine.txt文件中会有相应的输出

异常情况测试:
需求1
参数不正确

输入的文件有误(会提示异常)


需求二
参数不正确

线路不存在时没有输出

需求三
参数不正确

站点输入不合理会提示异常

八、总结:
北京地铁线路规划这个项目很贴近生活、具有实际意义。这个项目让我学习了Dijkstra算法的使用,练习了java语言,结合北京的地铁线路来查看特定线路的站点,查询两个站点之间的最短路径,这一点与导航的app有异曲同工之处。将所学的知识与生活中的实际问题结合起来,会觉得更有价值。