苏州四号地铁线在今年四月份终于要开始运营了,正好路过我家。以后每次从上海回来,就可以直接做地铁到家门口了。
既然四号线快要开通了,那么以后我从家门口到园区怎么换乘呢?作为程序员的我,肯定会考虑用什么算法才能找到一条可以换乘的路线。

从这个方向上看,这应该是一个有向图的最短路径问题。所以,我选择Dijkstra算法。算法的定义,我简单阐述一下,在大学的数据结构课程上应该都已学过。
算法思想:
设G=(V,E)是一个带权有向图,把图中顶点集合V分成两组,第一组为已求出最短路径的顶点集合(用S表示,初始时S中只有一个源点,以后每求得一条最短路径 , 就将加入到集合S中,直到全部顶点都加入到S中,算法就结束了),第二组为其余未确定最短路径的顶点集合(用U表示),按最短路径长度的递增次序依次把第二组的顶点加入S中。在加入的过程中,总保持从源点v到S中各顶点的最短路径长度不大于从源点v到U中任何顶点的最短路径长度。此外,每个顶点对应一个距离,S中的顶点的距离就是从v到此顶点的最短路径长度,U中的顶点的距离,是从v到此顶点只包括S中的顶点为中间顶点的当前最短路径长度。

首先,我初始化一些地铁站点的初始数据。
private static List<Station> line1 = new ArrayList<Station>();//1号线
private static List<Station> line2 = new ArrayList<Station>();//2号线
private static List<Station> line4 = new ArrayList<Station>();//4号线
private static Set<List<Station>> lists = new HashSet<List<Station>>();//所有线集合
private static int totalStaion = 0;//总的站点数量
private List<Station> outList = new ArrayList<Station>();//记录已经分析过的站点
static {
//1号线
String line1Str = "木渎站、金枫路站、汾湖路站、玉山路站、苏州乐园站、塔园路站、滨河路站、西环路站、桐泾北路站、广济南路站、养育巷站、乐桥站、临顿路站、相门站、东环路站、中央公园站、星海广场站、东方之门站、文化博览中心站、时代广场站、星湖街站、南施街站、星塘街站、钟南街站";
String[] line1s = line1Str.split("、");
for(String s : line1s){
line1.add(new Station(s));
}
for(int i =0;i<line1.size();i++){
if(i<line1.size()-1){
line1.get(i).next = line1.get(i+1);
line1.get(i+1).prev = line1.get(i);
}
}
//2号线
String line2Str = "高铁苏州北站、大湾站、富元路站、蠡口站、徐图港站、阳澄湖中路站、陆慕站、平泷路东站、平河路站、苏州火车站、山塘街站、石路站、广济南路站、三香广场站、劳动路站、胥江路站、桐泾公园站、友联站、盘蠡路站、新家桥站、石湖东路站、宝带桥南站";
String[] line2s = line2Str.split("、");
for(String s : line2s){
line2.add(new Station(s));
}
for(int i =0;i<line2.size();i++){
if(i<line2.size()-1){
line2.get(i).next = line2.get(i+1);
line2.get(i+1).prev = line2.get(i);
}
}
//4号线
String line4Str =
"龙道浜站、张庄站、姚祥站、活力岛站、孙武纪念园站、平泷路西站、苏锦站、苏州火车站、北寺塔站、察院场站、乐桥站、三元坊站、南门站、人民桥南站、团结桥站、宝带路站、石湖东路站、红庄站、清树湾站、花港站、江陵西路站、江兴西路站、流虹路站、笠泽路站、顾家荡站、苏州湾东站、松陵大道站、吴江人民广场站、吴江汽车站、庞金路站、同里站";
String[] line4s = line4Str.split("、");
for(String s : line4s){
line4.add(new Station(s));
}
for(int i =0;i<line4.size();i++){
if(i<line4.size()-1){
line4.get(i).next = line4.get(i+1);
line4.get(i+1).prev = line4.get(i);
}
}
lists.add(line1);
lists.add(line2);
lists.add(line4);
totalStaion = line1.size() + line2.size() + line4.size();
}
核心的算法在这里,每次计算完一个最近的站点,然后再分析下一个最近的站点,直到结束。所以说Dijkstra又是使用贪心算法的策略。
/**
* 计算从start站到end站的最短经过路径
* @param start
* @param end
*/
public void search(Station start,Station end){
if(outList.size() == totalStaion){
printRoute(start,end);
return;
}
if(!outList.contains(start)){
outList.add(start);
}
//如果起点站的OrderSetMap为空,则第一次用起点站的前后站点初始化之
if(start.getOrderSetMap().isEmpty()){
List<Station> linkedstations = getAllLinkedStations(start);
for(Station s : linkedstations){
start.getAllPassedStations(s).add(s);
}
}
Station shortest = getShortestPath(start);//获取距离起点站start最近的一个站(有多个的话,随意取一个)
if(shortest == end){
printRoute(start,end);
return;
}
for(Station station : getAllLinkedStations(shortest)){
if(outList.contains(station)){ // 已经分析过的station,不再做任何分析
continue;
}
int shortestPath = (start.getAllPassedStations(shortest).size()-1) + 1;//前面这个1表示计算路径需要去除自身站点,后面这个1表示增加了1站距离
if(start.getAllPassedStations(station).contains(station)){
//如果start已经计算过到此station的距离,那么比较出最小的距离
if((start.getAllPassedStations(station).size()-1) > shortestPath){
//重置start到周围各站的最小路径
start.getAllPassedStations(station).clear();
start.getAllPassedStations(station).addAll(start.getAllPassedStations(shortest));
start.getAllPassedStations(station).add(station);
}
} else {
//如果start还没有计算过到此station的距离
start.getAllPassedStations(station).addAll(start.getAllPassedStations(shortest));
start.getAllPassedStations(station).add(station);
}
}
outList.add(shortest);
search(start,end);//重复计算,往外面站点扩展
}
/**
* station站到各个站的最短距离,相隔1站,距离为1,依次类推
* @param station
* @return
*/
private Station getShortestPath(Station station){
int minPatn = Integer.MAX_VALUE;
Station rets = null;
for(Station s :station.getOrderSetMap().keySet()){
if(outList.contains(s)){ // 已经分析过的station,不再做任何分析
continue;
}
LinkedHashSet<Station> set = station.getAllPassedStations(s);//参数station到s站所经过的所有站点的集合
if(set.size() < minPatn){
minPatn = set.size();
rets = s;
}
}
return rets;
}
/**
* 获取参数station直接相连的所有站,包括交叉线上面的站
* @param station
* @return
*/
private List<Station> getAllLinkedStations(Station station){
List<Station> result = new ArrayList<Station>();
for(List<Station> line : lists){
if(line.contains(station)){
Station s = line.get(line.indexOf(station));
if(s.prev != null){
result.add(s.prev);
}
if(s.next != null){
result.add(s.next);
}
}
}
return result;
}
/**
* 打印实际的线路图
* @param start
* @param end
*/
private void printRoute(Station start,Station end) {
StringBuilder sb = new StringBuilder();
sb.append("找到:"+end.getName()+",共经过"+(start.getAllPassedStations(end).size()-1)+"站").append("\r\n");
for(Station station : start.getAllPassedStations(end)){
sb.append(station.getName()+"->");
}
sb.delete(sb.length()-2,sb.length());
System.out.println(sb.toString());
}
我们来看看如何使用这个程序,从南门站到星湖街站应该如何换乘。
subway.search(new Station("南门站"), new Station("星湖街站"));
得到如下的结果:

总结
Dijkstra只是一种最短路径的算法,除此之外还有Floyd算法、Bellman-Ford算法、SPFA算法等等。而且真实的地铁换乘算法,还要考虑到高峰时地铁人流、换乘时需要走的路(特别是上海的地铁某些站点换乘时需要走很多的路)、每一站所花费的时间、总共花费的时间和金钱...
因此,本文只是模拟了地铁换乘,只是一种实现思路而已。