在求图线任意两点间最短路径时,利用floyd、dijdstra等成熟的算法可以求得,效率还不错。但要求换乘最少、最舒适等路径时,需要求线网图中任意两个点的所有路径,然后根据条件筛选,以上算法无能为力。本人最近做个小项目需要用到这个需求,因此在网上搜索相关资料,找到一个利用栈采用深度优先搜索的算法,利用此算法在下图11条线路190余个站中测试,任意两点间所有路径平均耗时15秒,不能满足需求。
于是,自己琢磨着写了一个算法,现将其记录如下:
第一步:将每条线路视为一个结点,生成所有乘坐线路顺序列表。如上图中楚河汉街——中山公园的路径中,首先采用广度优先搜索生成[[4,2],[4,8,1,2],[4,3,2],......],这样的路径,在生成中剔除明显不合理的路径,如[4,2,7,1,2]这样的路径。
第二步:求第一步生成的线路顺序中相邻两条线的交点(即换乘站),生成以换乘站作为节点的顺序路径。如第一步中[4,3,2]这条线路顺序,4和3的交点为王家湾,3和2的交点为范湖和宏图大道,因此可生成[楚河汉街,王家湾,范湖,中山公园]以及[楚河汉街,王家湾,宏图大道,中山公园]这两起点、终点以及换乘站构成的路径。
第三步:将第二步中每条路径相邻两个站点之间其他站点补齐,即是从起点至终点的完整路径。
因为在第一步剔除了许多不合理路径,因此最后生成所有路径中比以站点深度优先搜索得出来的所有路径少得多,前者为1000余条,后者为15万条。不过没关系,剔除的路径对我们来说没有任何意义。
算法中适用存在环线的线网图,将Line对象中的isCircle设置为true即可。
由于篇幅原因,以下只贴算法中的关键代码,完整的项目以点击下载图中任意两点间所有路径高效算法
首先介绍下算法中存储路径的数据结构:树
Tree:
public class SolutionTree {
private TreeNode root;//权的根节点
public SolutionTree(int Id)
{
root = new TreeNode(Id);
}
public TreeNode getRoot() {
return root;
}
public void setRoot(TreeNode root) {
this.root = root;
}
}
TreeNode:
public class TreeNode implements Serializable {
/**
*
*/
private static final long serialVersionUID = 656317088559367582L;
private int Id; //权节点id
protected TreeNode parentNode;//父节点
protected List<TreeNode> childList;//子节点列表
public TreeNode(int Id) {
this.Id=Id;
initChildList();
}
public TreeNode(int Id,TreeNode parentNode) {
this.Id=Id;
this.parentNode=parentNode;
initChildList();
}
//此处省略get、set以及一些内部方法
}
然后是存储站点信息以及线路信息的Class:
Station.class:
public class Station {
private int id;//车站id
private String name;//车站名
private Station nextSta;//下个站
private Station prevSta;//上个站
private int line;//车站所在的线路
private List<Integer> transferLines = new ArrayList<Integer>();//当前站可换乘线路列表,不是换乘,列表中只有一个0元素
//此外省略了get、set以及一些内部方法
}
Line.class:
public class Line {
private int id; //线路id
private List<Station> stationList = new ArrayList<Station>();//本条线的车站列表
private boolean isCircle ;//是否为环线
//此外省略了get、set以及一些内部方法
}
最后就是算法了,算法中需要三个初始数据,graph:站点列表,将每个站点对象中的所有属性按实际情况赋值;matrix:费用矩阵,可连通,设置为连通的费用值,不可连通,设置为无穷大,算法中统一相邻站点费用为2,换乘站连接费用为5,实际使用中可读取费用数据后赋值;lineTable:线路列表,线路id作为key值。以上三个初始数据在实际使用中可通过第二个构造函数传入赋值。代码如下:
public class PathSearch {
private List<Station> graph; //车站列表,
private static final int INF=Integer.MAX_VALUE;
private int[][] matrix;//费用矩阵
private Map<Integer,Line> lineTable = new HashMap<Integer,Line>();//线路表
/**
* 从数据库或文件读取站点数据,费用表等数据,初始化数据
*/
public PathSearch()
{
try
{
//要求根据库中的车站按所在线中的顺序依次存储
String sql = "Select * From tab_station order by id";
List<Map<String,Object>> result = MyDatabase.search(sql);
graph = new ArrayList<Station>();
List<Station> temp = new ArrayList<Station>();
int line = Integer.parseInt(result.get(0).get("line").toString());
for(int i=0;i<result.size();i++)
{
Map<String,Object> data = result.get(i);
Station sta = new Station();
sta.setId(i);
sta.setLine(Integer.parseInt(data.get("line").toString()));
sta.setName(data.get("name").toString());
if(!(line == sta.getLine()) || i==result.size()-1)
{
Line tl = new Line();
tl.setId(line);
tl.setStationList(temp);
//若线路为环线,在此设置 tl.setCircle(true);
lineTable.put(line, tl);
line = sta.g