蓝桥杯中的地铁换乘问题
如题:
为解决交通难题,某城市修建了若干条交错的地铁线路,线路名及其所属站名如stations.txt所示。
线1 苹果园 .... 四惠东
线2 西直门 车公庄 .... 建国门
线4 ....
其中第一行数据为地铁线名,接下来是该线的站名。 当遇到空行时,本线路站名结束。
下一行开始又是一条新线....直到数据结束。
如果多条线拥有同一个站名,表明:这些线间可以在该站换车。
为引导旅客合理利用线路资源,解决交通瓶颈问题,该城市制定了票价策略:
1. 每条线路可以单独购票,票价不等。 2. 允许购买某些两条可换乘的线路的联票。联票价格低于分别购票。
单线票价和联合票价如 price.txt 所示。
线1 180 ..... 线13 114 线1,线2 350 线1,线10 390 .....
每行数据表示一种票价 线名与票价间用空格分开。如果是联票,线名间用逗号分开。 联票只能包含两条可换乘的线路。
现在的问题是:根据这些已知的数据,计算从A站到B站最小花费和可行的换乘方案。
比如,对于本题目给出的示例数据
如果用户输入: 五棵松,奥体中心
程序应该输出: -(线1,线10)-线8 = 565
如果用户输入: 五棵松,霍营
程序应该输出: -线1-(线4,线13) = 440
可以看出,用户输入的数据是:起始站,终到站,用逗号分开。 程序输出了购票方案,在括号中的表示联票,短横线(-)用来分开乘车次序。 等号后输出的是该方案的花费数值。
请编程解决上述问题。 注意: 1. 我们测试您的程序时,所用数据与题目中的示例数据不同,但格式完全一样。 2. 当多个方案有相同的最小花费,输出任意一个方案即可。
要求考生把所有类写在一个文件中。 调试好后,存入与考生文件夹下对应题号的“解答.txt”中即可。 相关的工程文件不要拷入。请不要使用package语句。
另外,源程序中只能出现JDK1.5中允许的语法或调用。不能使用1.6或更高版本。
核心思路:首先根据读取的文件建立路线的邻接矩阵, 然后穷举所有符合条件的路径, 最后选取最优的。
package final_match; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; /** * 解决地铁线路的乘换问题 * @author wangjing * */ public class ChooseTrainNum { // 保存线路及该线路的所有站台 private Map<String, ArrayList<String>> stations = new HashMap<>(); // 保存每条线路的票价及联票票价 private Map<String, Integer> price = new HashMap<>(); // 读取文件的流 private FileReader fr = null; // 邻接矩阵(表示无向图) private int[][] matrix = null; // 顶点的值 private Object[] vexs = null; // 查找路径的标记 private boolean[] visited = null; /** * 读取车子以及票价文件 * @param f * 给定文件 */ public void readFile(File f) { ArrayList<String> al = null; String tmp = ""; BufferedReader br = null; String name = f.getName(); String[] s = null; boolean flag = false; try { // 读取的是车子文件 if(name.startsWith("stations")) al = new ArrayList<>(); fr = new FileReader(f); br = new BufferedReader(fr); while((tmp = br.readLine()) != null) { // 如果条件成立,表示该次读取结束, 准备开始读取下一条线路、 // (针对读取车站而言的) if(tmp.equals("")) { al = new ArrayList<>(); flag = false; continue; } if(!flag) { if(name.startsWith("stations")) stations.put(tmp, al); else { s = tmp.split(" "); price.put(s[0], Integer.parseInt(s[1])); } flag = true; } else { if(name.startsWith("stations")) al.add(tmp); else { s = tmp.split(" "); price.put(s[0], Integer.parseInt(s[1])); } } } // end while // 如果读取的是车站文件, 创建邻接矩阵 if(name.startsWith("stations")) createMatrix(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { if(br != null) br.close(); if(fr != null) fr.close(); } catch(IOException e) { e.printStackTrace(); } } } /** * 根据路线,创建邻接矩阵(存储无向图) */ private void createMatrix() { int len = stations.size(); vexs = stations.keySet().toArray(); matrix = new int[len][len]; int i = 0, j = 0; ArrayList<String> row = null; ArrayList<String> col = null; for(; i<len; i++) { row = stations.get(vexs[i]); for(j=0; j<len; j++) { col = stations.get(vexs[j]); if(isIntersect(row, col)) { matrix[i][j] = 1; matrix[j][i] = 1; } // end if } // end for } // end for } /** * 判断给定的两条线路是否有交集 * @param row * 给定的线路 * @param col * 给定的线路 * @return * 如果有交集,返回true;否则返回false */ private boolean isIntersect(ArrayList<String> row, ArrayList<String> col) { if(row == col) return false; int len = col.size(), i = 0; for(; i<len; i++) if(row.contains(col.get(i))) return true; return false; } /** * 得到最优方案 * @param begin * 始发站 * @param end * 终点站 * @return * 从始发站到终点站的最优方案 */ public String optimalScheme(String begin, String end) { ArrayList<String> bgn = location(begin); ArrayList<String> ed = location(end); int len1 = bgn.size(), len2 = ed.size(); int i = 0, j = 0; int m = 0, n = 0; ArrayList<String[]> tmp = null; String[] st = null; ArrayList<ArrayList<String[]>> result = new ArrayList<>(); visited = new boolean[vexs.length]; st = new String[vexs.length + 1]; for(; i<len1; i++) { m = indexOf(bgn.get(i)); for(j=0; j<len2; j++) { tmp = new ArrayList<>(); n = indexOf(ed.get(j)); onePath(m, n, tmp, st, 0); result.add(tmp); } // end for } // end for // 用于测试 // len1 = result.size(); // for(i=0; i<len1; i++) { // tmp = result.get(i); // len2 = tmp.size(); // for(j=0; j<len2; j++) { // st = tmp.get(j); // for(m=0; m<st.length; m++) // System.out.print(st[m] + " "); // System.out.println(); // } // } return optimal(result); } /** * 从分析所得的所有结果中找到一个最优的解 * @param result * 所有的路线结果 * @return * 最优的路线方案 */ private String optimal(ArrayList<ArrayList<String[]>> result) { int size = result.size(), i = 0, j = 0; ArrayList<String[]> tmp = null; Object[] keys = price.keySet().toArray(); String s[] = null, ss[] = null; int len = 0; Map<String, Integer> map = new HashMap<>(); for(; i<size; i++) { tmp = result.get(i); len = tmp.size(); for(j=0; j<len; j++) { /****************************\ * 分别求的正向和负向的最小路径选取其中 * 最小花费的那一组, 进过处理后即为该次 * 的最优.(应为可以购买联票的线路无序无序) \****************************/ s = oneMin(tmp.get(j), keys); ss = oneMin(reverse(tmp.get(j)), keys); // 正向的结果,直接添加 if(Integer.parseInt(s[1]) <= Integer.parseInt(ss[1])) map.put(s[0], Integer.parseInt(s[1])); else { // 负向的结果,要change change(ss); map.put(ss[0], Integer.parseInt(ss[1])); } } } // end for // 从所有的结果中找取一个最优的 int min = 0, loc = 0; keys = map.keySet().toArray(); min = map.get(keys[0]); for(i=1; i<keys.length; i++) { if(min > map.get(keys[i])) { min = map.get(keys[i]); loc = i; } } // end for return keys[loc] + " = " + min; } /** * 处理给定的一条路径 * @param ss * ss[0]是路径,ss[1]是其总票价 */ private void change(String[] ss) { // 拆分 String[] s = ss[0].split("-"); int i = 0, j = s.length-1; String tmp = ""; // 交换 while(i <= j) { tmp = s[j]; s[j] = s[i]; s[i] = tmp; i ++; j --; } // end while tmp = ""; // 重新复合 for(i=0; i<s.length-1; i++) tmp += ('-' + s[i]); ss[0] = tmp; } /** * 将给定的路径翻转 * @param path * 给定的路径 * @return * 翻转过后的路径 */ private String[] reverse(String[] path) { String[] tmp = new String[path.length]; int i = 0, j = 0; while(i < path.length && path[i] != null) i ++; i --; for(j=0; i >=0; j++) tmp[j] = path[i--]; return tmp; } /** * 求的一条路径的最优组合(因为有联票影响) * 算法:<br /> * 每次都比较三个(不够的另外处理), 假设为a, b, c <br /> * 1. 如果 a 与 b 有联票<br /> * 1) 如果b 与 c有联票<br /> * (1) 如果money(a,b) < money(b, c)<br /> * total += money(a, b);<br /> * (2) 否则:total += money(a);<br /> * 2) 否则:<br /> * (1) total += money(a, b);<br /> * i ++;<br /> * 2:否则:<br /> * 1) 如果 path[a] != null, total += money(a)<br /> * 2) 否则,break;<br /> * @param path * 给定的路径站点数组 * @param keys * 所有的票价对应的线路数组 * @return * 该条线路的最优的组合([0]表示最优路径组合, [1]表示总的票价) */ private String[] oneMin(String[] path, Object[] keys) { int m1 = 0, m2 = 0, i = 0, loc = -1, money = 0; String way = ""; for(i=1; i<path.length; i++) { if((loc = findUnionTicket(path[i-1] + "," + path[i], keys)) != -1) { m1 = price.get(keys[loc]); if(i < path.length-1) { loc = findUnionTicket(path[i] + "," + path[i+1], keys); if(loc != -1) { m2 = price.get(keys[loc]); if(m1 < m2) { way += ("-(" + path[i-1] + "," + path[i] + ")"); money += m1; } else { way += ("-" + path[i-1]); money += price.get(path[i-1]); } // end if } else { money += m1; way += ("-(" + path[i-1] + "," + path[i] + ")"); i ++; } // end if } else { money += m1; way += ("-(" + path[i-1] + "," + path[i] + ")"); } // end if } else { if(path[i-1] != null) { way += ("-" + path[i-1]); money += price.get(path[i-1]); } else break; } // end if } return new String[]{way, money + ""}; } /** * 查找联票位置索引 * @param path * 站点的组合 * @param keys * 所有的票价对应的线路数组 * @return * 如果存在,返回位置, 否则返回 -1 */ private int findUnionTicket(String path, Object[] keys) { if(path == null) return -1; int i = 0; for(; i<keys.length; i++) if(path.equals(keys[i])) return i; return -1; } /** * 得到从begin到end中间所有合法路径组合 * (有点类似于图的深度优先算法(DFS)) * @param begin * 始发站的索引 * @param end * 终点站的索引 * @param tmp * 保存所有合法路径上的动态数组 * @param or * 临时储存路径的数组 * @param level * 递归的深度(要修改的or的下标) */ private void onePath(int begin, int end, ArrayList<String[]> tmp, String[] or, int level) { visited[begin] = true; or[level] = (String) vexs[begin]; int w = 0; for(w=firstAdjVex(begin); w>=0 && w!=end; w=nextAdjVex(begin, w)) { if(!visited[w]) onePath(w, end, tmp, or, level+1); } if(w == end) { or[level + 1] = (String) vexs[end]; tmp.add(or.clone()); or[level+1] = null; } or[level] = null; visited[begin] = false; } /** * 求的第 v行离w最近的符合要求的顶点索引 * @param v * 邻接矩阵的第v行 * @param w * 第v行的w索引 * @return * w的第一邻接点索引 */ private int nextAdjVex(int v, int w) { for(int j=w+1; j<vexs.length; j++) if(matrix[v][j] == 1) return j; return -1; } /** * 求的v的第一邻接点 * @param v * 第v行 * @return * v的第一邻接点索引 */ private int firstAdjVex(int v) { int j = 0; for(; j<vexs.length; j++) if(matrix[v][j] == 1) return j; return -1; } /** * 得到给定的顶点位置索引 * @param content * 给定的顶点 * @return * 返回位置 */ private int indexOf(String content) { int i = 0; for(; i<vexs.length; i++) if(vexs[i].equals(content)) return i; return -1; } /** * 定位车站所在的线路 * @param station * 给定车站 * @return * 进过该车站的所有线路 */ private ArrayList<String> location(String station) { ArrayList<String> rtn = new ArrayList<>(); Iterator<String> it = stations.keySet().iterator(); String tmp = ""; while(it.hasNext()) { tmp = it.next(); if(stations.get(tmp).contains(station)) rtn.add(tmp); } // end while return rtn; } /** * 打印邻接矩阵 */ public void printMatrix() { int i = 0, j =0; for(; i<matrix.length; i++) System.out.print(vexs[i] + " "); System.out.println(); for(i=0; i<matrix.length; i++) { for(j=0; j<matrix.length; j++) System.out.print(matrix[i][j] + "\t"); System.out.println(); } } public static void main(String[] args) throws IOException { ChooseTrainNum bk = new ChooseTrainNum(); File f = new File("price.txt"); bk.readFile(f); f = new File("stations.txt"); bk.readFile(f); // bk.printMatrix(); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String line = br.readLine(); String rs = ""; String[] tmp = null; tmp = line.split(","); rs = bk.optimalScheme(tmp[0], tmp[1]); System.out.println(rs); } }
测试截图:
(文件一测试结果)
(文件二测试结果)
(文件三测试结果)