地铁线路最短路径(实现版)


主要功能

根据所提供的地铁线路图(以北京地铁为例)

计算指定两站之间最短的乘车路线

地铁线路信息保存在 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)多线路(换乘)查询

总结

·对模块化的编程有了更多的体会,编码能力还需提升,新的数据结构还需学习。
·第一次尝试博客园上进行编写,了解了基本的网络平台编写流程,有了新的体验。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM