北京地铁两站点间最短路径实现

主要功能

提供一副如下所示的地铁线路图

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

计算指定两站之间最短(最少经过站数)乘车路线;输出指定地铁线路的所有站点。以北京地铁为例,地铁线路信息保存在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、第一次写博客,也是一种锻炼,在写博客的过程中也学到了很多

posted @ 2020-11-05 12:44  Bazinga001  阅读(699)  评论(0)    收藏  举报