主要功能
根据所提供的地铁线路图(以北京地铁为例)
计算指定两站之间最短的乘车路线。
地铁线路信息保存在 data.txt
中,格式如下:
地铁线路总数
线路名1 站名1 站名2 站名3 ...
线路名2 站名1 站名2 站名3 ...
线路名3 站名1 站名2 站名3 ......
最终项目给定两个站点,输出指定最短地铁线路的所有站点。
实现语言
Java(利用eclipse)
实现算法
由于本题涉及的图为无向无权图,故使用
广度优先搜索(BFS)算法
首先访问一个顶点,然后是所有与其相邻接的顶点,最后是所有与这些顶点邻接的顶点。每个顶点会确保仅访问一次
本题在遍历节点时会同时记录相关的途径站点数量和换乘数量。
类职责划分
1.Model
用于保存地铁线路信息的类
public class Route {
private String rname;//线路名称
private ArrayList<String> route = new ArrayList<>();//线路所含站点
}
用于保存地铁站点信息的类
public class Station {
//读取地铁信息时修改
private String sname;//站名
private ArrayList<String> bTR = new ArrayList<>();//线路名
private ArrayList<String> bs = new ArrayList<>();//邻站(距离为1的站)
//执行算法后更改
private Station ps;//前一个站点
private int dist;//距离(距起始站)
private int transNum;//换乘数
private int visited;//保存是否访问
}
2.Control
读取文件的类
public class ReadData {
//保存所读取的站点和线路信息,主函数中会引用
private ArrayList<Station> station= new ArrayList<>();
private ArrayList<Route> route= new ArrayList<>();
//读取文件的函数
public ReadData(String fileName);
}
执行BFS算法的函数
public class BFS {
//根据读入的始发站进行BFS遍历,得出最小路径图
public ArrayList<Station> FindMin(String firstStation,ArrayList<Station> station);
//根据读入的终点站输出从始发站到终点站的路径
public void shortPath(String endStation,ArrayList<Station> station);
3.Util
执行程序的主函数位置 public class Start
核心代码
以下代码为简化代码,完整代码已上传至Github
public ReadData(String fileName)
读取 data.txt
中地铁线路信息并保存
//线路部分
reader = new BufferedReader(new FileReader(file));
String tempString = null;
while((tempString = reader.readLine()) != null) {//读取每行
Route r = new Route();
String[] t = tempString.split(" ");//每行分为多个字符串(包括线路名和站点名)
r.setRname(t[0]);//首字符串为线路名
for(int i=1;i<t.length;i++) {
r.addRoute(t[i]);//剩下字符均为线路内站点名称
}
route.add(r);//将结果添加至结果集
}
//站点部分
String rname = route.get(i).getRname();
ArrayList<String> l = route.get(i).getRoute();//临时保存当前线路内部站点
//判断环线(若线路首尾相同则为环线)
if(l.get(0).equals(l.get(l.size()-1))) {
int flag=0;
//优先处理首尾站点
for(int k=0;k<station.size();k++) {
//若该站点曾经录入过,则添加相关信息
//有环线的站点首尾相同且需添加两个相邻站
if(station.get(k).getSname().equals(l.get(0))) {
station.get(k).setBTR(rname);//更新所属线路
//更新邻站
station.get(k).setBs(l.get(1));
station.get(k).setBs(l.get(l.size()-2));
flag=1;
break;
}
}
//若该站点从未录入,则新建
if(flag==0) {
Station s = new Station();
s.setSname(l.get(0));//添加站点名称
s.setBTR(rname);//添加所属线路
//添加邻站
s.setBs(l.get(1));
s.setBs(l.get(l.size()-2));
station.add(s);
}
}
//对于非环路,首尾两站仅添加一个相邻站点
//对于剩余站点,添加前后两个站点即可
//(该部分代码类似,在此省略)
public ArrayList<Station> FindMin(String firstStation,ArrayList<Station> station)
执行BFS遍历,结果返回修改原站点信息
ArrayList<Station> result = new ArrayList<>();//结果集
//找到始发站
int fsIndex=-1;
for(int i=0;i<station.size();i++) {
Station tmp = station.get(i);
if(tmp.getSname().equals(firstStation)) {//名称相同即找到
fsIndex=i;
break;
}
}
//若未找到则报错结束
if(fsIndex==-1) {
System.out.println("未找到该起始站点");
System.exit(0);//未找到则退出
return station;
}
//执行算法
Queue<Station> queue = new LinkedList<>();//链表模拟队列
station.get(fsIndex).setVisited(1);//标记访问
queue.offer(station.get(fsIndex));//初始站点入队列
int dist=0;//保存步数
while(!queue.isEmpty()) {
Station tmpS = queue.remove();//移出队列头部
if(dist==0) {//判断是不是队头
tmpS.setDist(dist);//存入步数
dist++;
}else {
//判断是否换乘
dist=tmpS.getPs().getDist();
tmpS.setDist(dist+1);
dist++;
}
result.add(tmpS);//结果集增加
ArrayList<Station> tmpBs = tmpS.getBs(station);
for(int i=0;i<tmpBs.size();i++) {
if(tmpBs.get(i).getVisited()==0) {//判断是否访问过
tmpBs.get(i).setPs(tmpS);//保存前置站点为当前站点
tmpBs.get(i).setVisited(1);//标记访问
queue.offer(tmpBs.get(i));//若未访问过则直接存入队列
}
}
}
return result;//返回结果集
public void shortPath(String endStation,ArrayList<Station> station)
根据终点站查找线路规划并输出
//找到终点站
int endIndex=-1;
for(int i=0;i<station.size();i++) {
Station tmp = station.get(i);
if(tmp.getSname().equals(endStation)) {
endIndex=i;
break;
}
}
//若未找到则报错结束
if(endIndex==-1) {
System.out.println("未找到该终点站");
System.exit(0);
return ;
}
站点数据中保存了该站点的前置站点,用此特性配合堆栈可以逆序输出(回到正常顺序)
Stack<Station> stack = new Stack<>();//建立栈以实现逆序输出
Station tmp = station.get(endIndex);//栈底为终点站
if(tmp.getDist()==0) {
System.out.println("该站为始发站");
return ;
}
int dist = tmp.getDist();//用于保存途经站点数
int transNum = 0;//用于保存换乘数
//逐步入栈
while(tmp.getPs()!=null) {
stack.push(tmp);
tmp=tmp.getPs();//更新为前置站点入栈
}
//判断换乘
ArrayList<String> r1 =tmp.getBTR();
ArrayList<String> r2 = stack.peek().getBTR();
String now="";//用于保存当前线路
int flag=0;
//寻找当前线路
for(int i=0;i<r1.size();i++) {
for(int j=0;j<r2.size();j++) {
if(r1.get(i).equals(r2.get(j))) {
now=r1.get(i);
flag=1;
break;
}
}
if(flag==1) {
break;
}
}
System.out.println("当前为:"+now);
System.out.print(tmp.getSname());
//逐步出栈
while(!stack.isEmpty()) {
//判断是否换乘
r1 = tmp.getBTR();
r2 = stack.peek().getBTR();
flag=0;
for(int i=0;i<r1.size();i++) {
for(int j=0;j<r2.size();j++) {
//若两个站点所共有的线路与当前线路不同,则为换乘线路
if(r1.get(i).equals(r2.get(j))&&(!now.equals(r1.get(i)))) {
now=r1.get(i);//更改当前线路
flag=1;
break;
}
}
if(flag==1) {
break;
}
}
if(flag==1) {
tmp=stack.peek();
System.out.println();
System.out.println("转至:"+now);
System.out.print(stack.pop().getSname());
transNum++;
}else {
tmp=stack.peek();
System.out.print("-->"+stack.pop().getSname());
}
}
System.out.println();
dist--;
System.out.println("途径站数"+dist);
System.out.println("换乘数"+transNum);
主函数
public static void main(String[] args) throws Exception{
Scanner input = new Scanner(System.in);
//读取文件
ReadData file = new ReadData(FILEPATH);
//提取所保存的站点和线路信息
ArrayList<Station> station= file.getStation();
ArrayList<Route> route= file.getRoute();
//输出所有线路名称
for(int i=0;i<route.size();i++) {
System.out.print(route.get(i).getRname()+" ");
}
System.out.println();
//输出菜单
System.out.println("请选择所需任务: 1.查询线路 2.规划路线");
//输入
int choose = input.nextInt();
if(choose==1) {
System.out.print("请输入所需查询的线路名: ");
String name = input.next();
int index=-1;
for(int i=0;i<route.size();i++) {
if(route.get(i).getRname().equals(name)) {
index=i;
break;
}
}
if(index==-1) {
System.out.println("该线路名错误");
}else {
System.out.println(route.get(index).getRname()+":"+route.get(index).allRoute());
}
}else if(choose==2) {
BFS bfs = new BFS();
System.out.print("请输入始发站: ");
String start = input.next();
station=bfs.FindMin(start,station);
System.out.print("请输入终点站: ");
String end = input.next();
bfs.shortPath(end, station);
}
}
测试用例
1.查找线路
(1)正常查询
(2)线路不存在
2.规划路线
(1)始发站不存在
(2)终点站不存在
(3)始发站与终点站相同
(4)同线路(无换乘)查询
(5)多线路(换乘)查询
总结
·对模块化的编程有了更多的体会,编码能力还需提升,新的数据结构还需学习。
·第一次尝试博客园上进行编写,了解了基本的网络平台编写流程,有了新的体验。