地铁线路图中任意两点间所有路径高效算法

在求图线任意两点间最短路径时,利用floyd、dijdstra等成熟的算法可以求得,效率还不错。但要求换乘最少、最舒适等路径时,需要求线网图中任意两个点的所有路径,然后根据条件筛选,以上算法无能为力。本人最近做个小项目需要用到这个需求,因此在网上搜索相关资料,找到一个利用栈采用深度优先搜索的算法,利用此算法在下图11条线路190余个站中测试,任意两点间所有路径平均耗时15秒,不能满足需求。

地铁线路图中任意两点间所有路径高效算法_第1张图片

于是,自己琢磨着写了一个算法,现将其记录如下:

第一步:将每条线路视为一个结点,生成所有乘坐线路顺序列表。如上图中楚河汉街——中山公园的路径中,首先采用广度优先搜索生成[[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 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 transferLines = new ArrayList();//当前站可换乘线路列表,不是换乘,列表中只有一个0元素
	
    //此外省略了get、set以及一些内部方法

}

Line.class:

public class Line {

	private int id; //线路id
	private List stationList = new ArrayList();//本条线的车站列表
	private boolean isCircle ;//是否为环线

     //此外省略了get、set以及一些内部方法

}

最后就是算法了,算法中需要三个初始数据,graph:站点列表,将每个站点对象中的所有属性按实际情况赋值;matrix:费用矩阵,可连通,设置为连通的费用值,不可连通,设置为无穷大,算法中统一相邻站点费用为2,换乘站连接费用为5,实际使用中可读取费用数据后赋值;lineTable:线路列表,线路id作为key值。以上三个初始数据在实际使用中可通过第二个构造函数传入赋值。代码如下:

public class PathSearch {  

	private List graph;  //车站列表,
	private static final int INF=Integer.MAX_VALUE;
	private int[][] matrix;//费用矩阵
	private Map lineTable = new HashMap();//线路表
	
	/**
	 * 从数据库或文件读取站点数据,费用表等数据,初始化数据
	 */
	public PathSearch()
	{
		try
		{
			//要求根据库中的车站按所在线中的顺序依次存储
			String sql = "Select * From tab_station order by id";
			List> result = MyDatabase.search(sql);
			graph = new ArrayList();
			List temp  = new ArrayList();
			int line = Integer.parseInt(result.get(0).get("line").toString());
			for(int i=0;i 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.getLine();
					if(i==result.size()-1)
					{
						temp.add(sta);
					}
					temp  = new ArrayList();
					
				}
				temp.add(sta);
				List list = Arrays.asList(data.get("transer").toString().replace(" ", "").split(","));
				for(String tl : list)
				{
					sta.addTransferLine(Integer.parseInt(tl));
				}
				
				graph.add(sta);
			}
			
			//将前后车站相连,graph中的元素也随着改变
			for(int key : lineTable.keySet())
			{
				Line tl = lineTable.get(key);
				int lineLength = tl.getStationList().size();
				if(tl.isCircle())//如果是环线,首尾相连
				{
					tl.getStationList().get(0).setPrevSta(tl.getStationList().get(lineLength-1));
					tl.getStationList().get(lineLength-1).setNextSta(tl.getStationList().get(0));
					
				}
				tl.getStationList().get(0).setNextSta(tl.getStationList().get(1));
				tl.getStationList().get(lineLength-1).setPrevSta(tl.getStationList().get(lineLength-2));
				
				for(int i=1;i graph,int[][] matrix,Map lineTable)
	{
		this.graph = graph;
		this.matrix = matrix;
		this.lineTable = lineTable;
	}

	
	//记录每对起始id第一次调用getAllPath方法得到数据,若相同的起始id再次再次调用时,可直接调用XXNoMakeAllPath()方法
	private List> allPath;
	
	/**
	 * 生成起点到终点的所有路径,该方法返回的所有路径比实际的所有路径要少得多,因为在方法中去除了一些明显不合理的路径,
	 * 例如2号线转3号线,再从3号线转2号线的路径明显不合理
	 * @param startStaId 起点id
	 * @param endStaId  终点id
	 * @return  径路列表
	 */
	private List> getAllPath(int startStaId, int endStaId)
	{
		Station startSta = graph.get(startStaId);
		Station endSta = graph.get(endStaId);
		
		//以起始线路id作为根节点初始化一棵树
		SolutionTree tree = new SolutionTree(startSta.getLine());
		makeTreeNode(tree.getRoot(),endSta.getLine());
		
		//获取树的所有叶子节点
		
		List ziYeList = new ArrayList(); 
		tree.getRoot().getZiYe(ziYeList);
		
		
		Iterator tempList = ziYeList.iterator();
		//删除id不是终点站的叶子结点
		while(tempList.hasNext())
		{
			TreeNode node = tempList.next();
			if(node.getId()!=endSta.getLine())
			{
				tempList.remove();
			}
		}
		
		//每个叶子节点的父辈节点反转后再加上节点本身,即为从起点到终点经过线路顺序
		List> tempResult = new ArrayList>();
		for(TreeNode node : ziYeList)
		{
			List list = node.getEldersID();
			Collections.reverse(list);
			list.add(node.getId());
			tempResult.add(list);
		}
		
		
		//通过每条线路顺序,获取换乘站的顺序路径
		List> staTempResult = new ArrayList>();
		for(List temp : tempResult)
		{
			
			//以起点站id作根要点id初始化树
			SolutionTree staTree = new SolutionTree(startStaId);
			
			for(int i = 0; i < temp.size()-1; i++)
			{
				List fatherList = new ArrayList();
				staTree.getRoot().getZiYe(fatherList);
				Line line = lineTable.get(temp.get(i));
				Line nextLine = lineTable.get(temp.get(i+1));
				//两条线的交点即为换乘站,
				List jiaoDianList = getJiaoDianZhan(line,nextLine.getId());
				
				//为每个叶子节点增加子节点
				for(TreeNode father : fatherList)
				{
					
					for(int staId : jiaoDianList)
					{
						//如果交点站为起始车站,则将换乘站所有下条线路的id作为一个节点添加到父节点中
						if(staId == startStaId)
						{
							Station nextSta = nextLine.getStation(line.getStation(staId).getName());
							TreeNode node2 = new TreeNode(nextSta.getId(), father);
							father.addChildNode(node2);
						}
						//否则,则换乘站所有当前线路的id作为一个节点添加到父节点中,同时将换乘站所有下条线路的id作为一个节点添加到刚添加的节点中
						else
						{
							TreeNode node1 = new TreeNode(staId, father);
							father.addChildNode(node1);
							Station nextSta = nextLine.getStation(line.getStation(staId).getName());
							TreeNode node2 = new TreeNode(nextSta.getId(), node1);
							node1.addChildNode(node2);
						}
						
					}
				}
				
			}
			List fatherList = new ArrayList();
			staTree.getRoot().getZiYe(fatherList);
			//将所有叶子节点的父辈节点反转并添加该叶子节点id即为第i条线路顺序所经过的换乘站顺序id
			for(TreeNode node : fatherList)
			{
				List list = node.getEldersID();
				Collections.reverse(list);
				list.add(node.getId());
				if(!list.contains(endStaId))
				{
					list.add(endStaId);
				}
				staTempResult.add(list);
				//System.out.println(list);
			}
		}
		
		
		List> result = new ArrayList>();
		for(List temp : staTempResult)
		{
			List path = new ArrayList();
			
			for(int i=0;i> tempPath = lineTable.get(sta.getLine()).getPathInLine(sta.getId(), nextSta.getId());
					if(tempPath.size()==1)
					{
						path.addAll(tempPath.get(0));
					}
					else
					{
						int minIndex = -1;
						int minDistance = Integer.MAX_VALUE;
						for(int index=0;index distance)
							{
								minIndex = index;
								minDistance = distance;
							}
						}
						if(minIndex!=-1)
						{
							path.addAll(tempPath.get(minIndex));
						}
					}
					
				}
				else if(i==temp.size()-2)
				{
					path.add(nextSta.getId());
				}
			}
			result.add(path);
		}
		allPath = result;
		
		return result;
	}

	/**
	 * 获取两条线路的交点
	 * @param line 当前线路id
	 * @param nextLineId  下条线路id
	 * @return  返回两条线路交点集合列表
	 */

	private List getJiaoDianZhan(Line line, int nextLineId) {
		// TODO Auto-generated method stub
		List result = new ArrayList();
		//为同一条,返回空列表
		if(line.getId() == nextLineId)return result;
		
		for(Station sta : line.getStationList())
		{
			//当前线路的车站的换乘列表中包含下条线路,表明有交集
			if(sta.getTransferLines().contains(nextLineId))
			{
				result.add(sta.getId());
			}
		}
		return result;
	}

	/**
	 * 为当前树节点添加子节点
	 * @param father 当前节点
	 * @param endLine 终点所在的线路id,用于判断递归结束条件
	 */

	private void makeTreeNode(TreeNode father,int endLine) {
		// TODO Auto-generated method stub
		List transferList = getXiangGuanXianLu(father,endLine);
		for(int line : transferList)
		{
			TreeNode node = new TreeNode(line,father);
			father.addChildNode(node);
			//递归直至当前线路等于终点站所在的线路
			if(line!=endLine)
			{
				makeTreeNode(node, endLine);
			}
		}
	}


	/**
	 * 获取与当前线路有交集的线路id
	 * @param father 当前线路的树节点
	 * @param endLine 终点所在的线路id
	 * @return 返回与当前线路有交集的线路id列表
	 */
	private List getXiangGuanXianLu(TreeNode father,int endLine) {
		// TODO Auto-generated method stub
		List result = new ArrayList();
		Line line = lineTable.get(father.getId());
		
		//获取当前节点的父辈节点id列表
		List temp = father.getEldersID();
		for(Station sta : line.getStationList())
		{
			for(int tl : sta.getTransferLines())
			{
				//如果是换乘站,列表中不存在当前相关的线路id,
				//除当前相关的线路id为终点点所在的线路id外,当前节点的父辈节点中不能包含此id,同时此id不能与当前节点的id相同
				//满足以上条件,才能作为当前节点的子节点
				if(tl!=0 && !result.contains(tl) && (tl == endLine || 
						(!temp.contains(tl) && tl != father.getId())))
				{
					result.add(tl);
				}
			}
		}
		return result;
	}


	/**
	 * 根据费用矩阵,获取路径的费用总和
	 * @param path 路径列表
	 * @return 返回路径的费用总和
	 */
	private int getDistance(List path)
	{
		int sum = 0;
		for(int i=0;i> makeShortPath(List> allPath)
	{
		if(allPath.size()==0)return allPath;
		List distance = new ArrayList();
		for(List path : allPath)
		{
			distance.add(getDistance(path));
		}
		float minDistance = Collections.min(distance);
		List> result = new ArrayList>();
		for(int i=0;i> makeTransferLessPath(List> allPath)
	{
		if(allPath.size()==0)return allPath;
		List transferNumList = new ArrayList();
		for(List path : allPath)
		{
			int sum = 0;
			for(int i=0;i> result = new ArrayList>();
		for(int i=0;i> getShortPath(int startStaId, int endStaId) {

		List> allPath = getAllPath(startStaId, endStaId);
		
		List> result = new ArrayList>();
		//获取路径最短的路径
		 List> temp = makeShortPath(allPath);
		 
		 //如果有两条及以上的最短路径,获取换乘最少的路径
		 if(temp.size()>1)
		 {
			 temp = makeTransferLessPath(temp);
		 }
		for(int i=0;i> getShortPathNoMakeAllPath(int startStaId, int endStaId) {

		
		List> result = new ArrayList>();
		
		if(allPath==null)return result;
		//获取路径最短的路径
		 List> temp = makeShortPath(allPath);
		 
		 //如果有两条及以上的最短路径,获取换乘最少的路径
		 if(temp.size()>1)
		 {
			 temp = makeTransferLessPath(temp);
		 }
		for(int i=0;i> getTransferLessPathNoMakeAllPath(int startStaId, int endStaId) {
		// TODO Auto-generated method stub
		//List> allPath = getAllPath(startStaId, endStaId);
		List> result = new ArrayList>();
		if(allPath==null)return result;
		//获取换乘个数最少的路径
		List> temp = makeTransferLessPath(allPath);
		//如果有多条换乘个数一样的路径,获取其中路径最短的
		if(temp.size()>1)
		{
			temp = makeShortPath(temp);
		}
		for(int i=0;i> getTransferLessPath(int startStaId, int endStaId) {
		// TODO Auto-generated method stub
		List> allPath = getAllPath(startStaId, endStaId);
		List> result = new ArrayList>();
		//获取换乘个数最少的路径
		List> temp = makeTransferLessPath(allPath);
		//如果有多条换乘个数一样的路径,获取其中路径最短的
		if(temp.size()>1)
		{
			temp = makeShortPath(temp);
		}
		for(int i=0;i transformPath(List path)
	{
		List result = new ArrayList();
		//获取起点站线路
		String startLine = getStartLine(path);
		for(int i=0;i1)
		{
			String direction = getTransferDirection(graph.get(path.get(0)),path.get(1));
			result.set(0, result.get(0)+"("+startLine+","+direction+")");
		}
		else
		{
			result.set(0, result.get(0)+"("+startLine+")");
		}
		
		
		
		return result;
	}

	
	/**
	 * 获取起点站需要乘坐的路径,起点站为换乘,返回的是换乘站所有的下条线路
	 * @param path 路径
	 * @return 返回起点站需要乘坐的路径
	 */
	private String getStartLine(List path) {
		// TODO Auto-generated method stub
		
		int n = 0;
		if(path.size()==1)
		{
			return "(已在目的地)";
		}
		Station sta1 = graph.get(path.get(n));
		Station sta2 = graph.get(path.get(n+1));
		while(sta1.getName().equals(sta2.getName()))
		{
			n++;
			sta1 = graph.get(path.get(n));
			sta2 = graph.get(path.get(n+1));
		}
		
		
		return "乘坐"+sta1.getLine()+"号线";
	}


	/**
	 * 获取换乘方向
	 * @param sta 换乘站
	 * @param nextId 换乘站的相邻车站id
	 * @return 换乘方向
	 */
	private String getTransferDirection(Station sta,Integer nextId) {
		// TODO Auto-generated method stub
		Line line = lineTable.get(sta.getLine());
		if(sta.getPrevSta()!=null && sta.getPrevSta().getId() == nextId)
		{
			return "往" + line.getStationList().get(0).getName() + "方向";
		}
		else if(sta.getNextSta()!=null && sta.getNextSta().getId() == nextId)
		{
			return "往" + line.getStationList().get(line.getStationList().size()-1).getName() + "方向";
		}
		else
		{
			return "";
		}
	}


	public List getGraph() {
		return graph;
	}




} 

 

测试类:

public static void main(String[] args) {
		// TODO 自动生成的方法存根
		try
		{
			long startTime = new Date().getTime();
			System.out.println("运行中....");
			PathSearch ps = new PathSearch();
			int start = 1;
			int end = 28;
			System.out.println("最短路:"+ps.getShortPath(start,end));
			System.out.println("换乘最少:"+ps.getTransferLessPathNoMakeAllPath(start,end));
			long endTime = new Date().getTime();
			System.out.print("耗时:"+(endTime-startTime)/1000.0+"秒");
		}
		catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		
		System.exit(0);
	}

}

 

版权声明:本文作者原创,未经允许不得他用,转载请注明出处。

你可能感兴趣的:(所有路径,路径算法,地铁,图)