目录

最短路径

Floyd(弗洛伊德)算法

Floyd简介

Floyd算法思想

Floyd样例

Floyd复杂度

Dijkstra算法

Dijkstra简介

Dijkstra算法思想

Dijkstra样例

Dijkstra复杂度

java实现

图的基础代码

Floyd算法

dijkstra算法

测试


最短路径

所谓最短路径问题是指:如果从图中某一顶点(源点)到达另一顶点(终点)的路径可能不止一条,如何找到一条路径使得沿此路径上各边的权值总和(称为路径长度)达到最小。

Floyd(弗洛伊德)算法

Floyd简介

Floyd算法是一个经典的动态规划算法。是解决任意两点间的最短路径(计算后可以找到所有点之间的最短路径)(称为多源最短路径问题)的一种算法,可以正确处理有向图或负权的最短路径问题。(动态规划算法是通过拆分问题规模,并定义问题状态与状态的关系,使得问题能够以递推(分治)的方式去解决,最终合并各个拆分的小问题的解为整个问题的解。)

Floyd算法思想

从任意节点i到任意节点j的最短路径不外乎2种可能:1)直接从节点i到节点j,2)从节点i经过若干个节点k到节点j

所以,我们假设arcs(i,j)为节点i到节点j的最短路径的距离,对于每一个节点k,我们检查arcs(i,k) + arcs(k,j) < arcs(i,j)是否成立

如果成立,证明从节点i到节点k再到节点j的路径比节点i直接到节点j的路径短,我们便设置arcs(i,j) = arcs(i,k) + arcs(k,j),

这样一来,当我们遍历完所有节点k,arcs(i,j)中记录的便是节点i到节点j的最短路径的距离。

(由于动态规划算法在执行过程中,需要保存大量的临时状态(即小问题的解),因此它天生适用于用矩阵来作为其数据结构,因此在本算法中,我们将不使用Guava-Graph结构,而采用邻接矩阵来作为本例的数据结构)

Floyd样例

java 两点最优路径 java最短路径问题_子节点

我们以一个4x4的邻接矩阵(二维数组arcs[ ][ ])作为图的数据结构。比如1号节点到2号节点的路径的权值为2,则arcs[1][2] = 2,2号节点无法直接到达4号节点,则arcs[2][4] = ∞(Integer.MAX_VALUE),则可构造如下矩阵:

java 两点最优路径 java最短路径问题_最短距离_02

根据以往的经验,如果要让任意两个顶点(假设从顶点a到顶点b)之间的距离变得更短,唯一的选择就是引入第三个顶点(顶点k),并通过顶点k中转(a -> k ->b)才可能缩短顶点a到顶点b之间的距离。

于是,现在的问题便分解为:求取某一个点k,使得经过中转节点k后,使得两点之间的距离可能变短,且还可能需要中转两个或者多个节点才能使两点之间的距离变短。

比如图中的4号节点到3号节点(4 -> 3)的距离原本是12(arcs[4][3] = 12),如果在只通过1号节点时中转时(4 -> 1 ->3),距离将缩短为11(arcs[4][1] + arcs[1][3] = 5 + 6 = 11)。

其实1号节点到3号节点也可以通过2号节点中转,使得1号到3号节点的路程缩短为5(arcs[1][2] + arcs[2][3] = 2 + 3 = 5),所以如果同时经过1号和2号两个节点中转的话,从4号节点到3号节点的距离会进一步缩短为10。

于是,延伸到一般问题:
1、当不经过任意第三节点时,其最短路径为初始路径,即上图中的邻接矩阵所示。
2、当只允许经过1号节点时,求两点之间的最短路径该如何求呢?只需判断arcs[i][1]+arcs[1][j]是否比arcs[i][j]要小即可。arcs[i][j]表示的是从i号顶点到j号顶点之间的距离,arcs[i][1] + arcs[1][j]表示的是从i号顶点先到1号顶点,再从1号顶点到j号顶点的路程之和。循环遍历一遍二维数组,便可以获取在仅仅经过1号节点时的最短距离

代码更新了两点之间经过1号节点的最短距离arcs[i][j],因此,数组中每两个节点之间对应距离都是最短的。由于此时arcs[i][j]的结果已经保存了中转1号节点的最短路径,此时如果继续并入2号节点为中转节点,则是任意两个节点都经过中转节点1号节点和2号节点的最短路径

因为运算完中转1号节点时,arcs[i][j]的结果已经更新为中转1号节点的最短路径了。更一般的,继续并入下一个中转节点一直到vexCount个时,arcs[i][j]的结果保存的就是整个图中两点之间的最短路径了。这就是Floyd算法的描述。

虽然此时已求得了节点的最短路径,但结果却不能明显的表达最终最短路径是中转了哪些节点,因此这里对应到动态规划算法中的强项——算法过程中可以完全记录所有的中间结果。

我们再定义一个二位数组path[][],其大小规模对应arcs[][],初始结果path[i][j] = j,表示节点i到节点j最后的中转节点是j。

在运算中是在判断arcs[i][k]+arcs[k][j]比arcs[i][j]要小时,我们进一步更新为:path[i][j] = path[i][k],即当前最短路径的最后中转节点是path[i][k]对应的节点(如果只允许中专一个节点时即为k,但中转多个节点时,需要对应上一步的中转节点,因此这里要指明是path[i][k]而不是k)。
于是我们通过向前递推path[][]数组,直到path[i][j]是目标节点。则可输出其中转节点

使用邻接表时,arcs二维数组变成,嵌套字典来表示邻接表,形式为{begin节点:{end节点:连线权重}}

path二维数组变成{begin节点:{end节点:最后的中转节点}}

Floyd复杂度

注意中间的那个三次循环,显然时间复杂度为O(V^3) ,V为顶点数量

空间复杂度为O(V^2),主要是两个嵌套的hashmap

Dijkstra算法

Dijkstra简介

迪杰斯特拉(Dijkstra)算法是典型最短路径算法,用于计算一个节点到其他节点(不是所有节点到所有节点)的最短路径。 它的主要特点是以起始点为中心向外层层扩展(广度优先搜索思想),直到扩展到终点为止

Dijkstra算法思想

通过Dijkstra计算图G中的最短路径时,需要指定起点s(即从顶点s开始计算)。

此外,引进两个集合S和U。S的作用是记录已求出最短路径的顶点(以及相应的最短路径长度),而U则是记录还未求出最短路径的顶点(以及该顶点到起点s的距离)。(u中间的距离如何计算,是通过s集合中的顶点作为中转点计算得出,s最开始只有一个起始点,但不断地从u中取出min距离的顶点到s,再根据这个顶点作为中转点更新u中的距离)

初始时,S中只有起点s;U中是除s之外的顶点,并且U中顶点的路径是”起点s到该顶点的路径”。然后,从U中找出路径最短的顶点,并将其加入到S中;接着,更新U中的顶点和顶点对应的路径。 然后,再从U中找出路径最短的顶点,并将其加入到S中;接着,更新U中的顶点和顶点对应的路径。 … 重复该操作,直到遍历完所有顶点。

Dijkstra样例

java 两点最优路径 java最短路径问题_java 两点最优路径_03

以上图G4为例,来对迪杰斯特拉进行算法演示(以第4个顶点D为起点)。以下B节点中23应为13。

java 两点最优路径 java最短路径问题_最短路径_04

java 两点最优路径 java最短路径问题_最短路径_05

java 两点最优路径 java最短路径问题_最短路径_06

java 两点最优路径 java最短路径问题_最短路径_07

初始状态:S是已计算出最短路径的顶点集合,U是未计算除最短路径的顶点的集合!

第1步:将顶点D加入到S中。 
此时,S={D(0)}, U={A(∞),B(∞),C(3),E(4),F(∞),G(∞)}。 注:C(3)表示C到起点D的距离是3。

第2步:将顶点C加入到S中。 
上一步操作之后,U中顶点C到起点D的距离最短;因此,将C加入到S中,同时更新U中顶点的距离。以顶点F为例,之前F到D的距离为∞;但是将C加入到S之后,F到D的距离为9=(F,C)+(C,D)。 
此时,S={D(0),C(3)}, U={A(∞),B(23),E(4),F(9),G(∞)}。

第3步:将顶点E加入到S中。 
上一步操作之后,U中顶点E到起点D的距离最短;因此,将E加入到S中,同时更新U中顶点的距离。还是以顶点F为例,之前F到D的距离为9;但是将E加入到S之后,F到D的距离为6=(F,E)+(E,D)。 
此时,S={D(0),C(3),E(4)}, U={A(∞),B(23),F(6),G(12)}。

第4步:将顶点F加入到S中。 
此时,S={D(0),C(3),E(4),F(6)}, U={A(22),B(13),G(12)}。

第5步:将顶点G加入到S中。 
此时,S={D(0),C(3),E(4),F(6),G(12)}, U={A(22),B(13)}。

第6步:将顶点B加入到S中。 
此时,S={D(0),C(3),E(4),F(6),G(12),B(13)}, U={A(22)}。

第7步:将顶点A加入到S中。 
此时,S={D(0),C(3),E(4),F(6),G(12),B(13),A(22)}。

此时,起点D到各个顶点的最短距离就计算出来了:A(22) B(13) C(3) D(0) E(4) F(6) G(12)。

Dijkstra复杂度

Dijkstra算法的时间复杂度为O(V^2),确实比Floyd算法小。当然,还有一点要注意,Dijkstra算法是不能解决具有负权边的图的。

空间复杂度为O(V)

 

java实现

图的基础代码


Floyd算法

/**使用Floyd(弗洛伊德)算法,返回所有节点间的最短距离<br>
	 * 设置两个map<br>
	 * 第一个 result,key为出发点,value是map,这个map的key是结束点,value是出发点到结束点的最短距离<br>
	 * 第二个 path,key为出发点,value是map,这个map的key是结束点,value是出发点到结束点的最短距离的路径的最后的中转节点<br>
	 * 一开始,result里的value为maxDouble(到自己的value为0),path里的value是结束点<br>
	 * 然后,用图里的所有的边对result做初始化,当不经过任意第三节点时,其最短路径为初始路径,只对图里先有的只经过两点的边,对result里的value更新<br>
	 * <br>
	 * 进行循环
	 * 当只允许经过1号节点时,求两点之间的最短路径该如何求呢?只需判断i到1号的min距离 + 1号到j的min距离是否比i到j的min距离要小即可。
	 * 如果i到1号的min距离 + 1号到j的min距离 小于 i到j的min距离,说明经过1号的路径更好,
	 * 让i到j的min距离=i到1号的min距离 + 1号到j的min距离,并且i到j的最后的中转节点为1号节点
	 * 比如说a到b到c到d,a到c的中转为b,a到d的中转为c(根据c得到a到c的中转为b,a到b的中转为b,就可以得到a到b到c到d)
	 * 循环遍历result,便可以获取在仅仅经过1号节点时的最短距离和中转节点。
	 * 由于此时result的结果已经保存了中转1号节点的最短路径,此时如果继续并入2号节点为中转节点
	 * 则是任意两个节点都经过中转节点1号节点和2号节点的最短路径,把所有节点作为中转节点后,得到的是所有节点间的最短距离
	 * 
	 * 
	 * @return  
	 */
	public Map<Vertex<T>, HashMap<Vertex<T>, Double>> getSmallestDistanceFloyd(){
		//第一个 result,key为出发点,value是map,这个map的key是结束点,value是出发点到结束点的最短距离
		Map<Vertex<T>, HashMap<Vertex<T>, Double>> result=new HashMap<>();
		//第二个 path,key为出发点,value是map,这个map的key是结束点,value是出发点到结束点的最短距离的路径的最后的中转节点
		Map<Vertex<T>, HashMap<Vertex<T>, Vertex<T>>> path=new HashMap<>();
		Set<Vertex<T>> vertexSet=getVertexSet();
		Vertex vertex;
		Edge edge;
		
		for(Vertex<T> begin:vertexSet){
			HashMap<Vertex<T>, Double> distanceMap=new HashMap<>();
			HashMap<Vertex<T>, Vertex<T>> pathMap=new HashMap<>();
			for(Vertex<T> end:vertexSet){
				//一开始,result里的value为maxDouble(到自己的value为0),path里的value是结束点
				distanceMap.put(end, Double.MAX_VALUE);
				pathMap.put(end, end);
			}
			//result里的value为maxDouble(到自己的value为0),path里的value是结束点
			distanceMap.put(begin, 0.0);
			result.put(begin, distanceMap);
			path.put(begin, pathMap);
		}
		
		for(Vertex<T> begin:vertexSet){
			HashMap<Vertex<T>, Double> distanceMap=result.get(begin);
			Iterator<Edge> edgeIterator=begin.getEdgeIterator();
			while(edgeIterator.hasNext()){
				edge=edgeIterator.next();
				//用图里的所有的边对result做初始化,当不经过任意第三节点时,其最短路径为初始路径,只对图里先有的只经过两点的边,对result里的value更新
				distanceMap.put(edge.getEndVertex(), edge.getWeight());
			}
			result.put(begin, distanceMap);
		}
		
		for(Vertex<T> mid:vertexSet){
			for(Vertex<T> begin:vertexSet){
				HashMap<Vertex<T>, Double> distanceMap=result.get(begin);
				HashMap<Vertex<T>, Vertex<T>> pathMap=path.get(begin);
				for(Vertex<T> end:vertexSet){
					Double beginEnd=distanceMap.get(end);
					Double beginMid=distanceMap.get(mid);
					Double midEnd=result.get(mid).get(end);
					if(beginMid==Double.MAX_VALUE||midEnd==Double.MAX_VALUE||beginMid+midEnd>beginEnd){
						//如果通过中转点不行,或者通过中转点的距离大于原先距离,就不考虑这个中转点
						continue;
					}
					//让i到j的min距离=i到1号的min距离 + 1号到j的min距离,并且i到j的最后的中转节点为1号节点
					distanceMap.put(end, beginMid+midEnd);
					pathMap.put(end, mid);										
				}
				result.put(begin, distanceMap);
				path.put(begin, pathMap);
			}						
		}
				
		for(Vertex<T> begin:vertexSet){
			HashMap<Vertex<T>, Double> distanceMap=result.get(begin);
			HashMap<Vertex<T>, Vertex<T>> pathMap=path.get(begin);
			for(Vertex<T> end:vertexSet){
				System.out.println("从顶点:"+begin.getLabel()+" ,到顶点:"+end.getLabel()+
						" ,最短距离为:"+distanceMap.get(end)+" ,最后中转点为:"+pathMap.get(end).getLabel());							
			}
		}
		
		return result;
	}

dijkstra算法

/**用dijkstra算法求出first节点到其他节点的最短距离<br>
	 * 声明两个set,open和close,open用于存储未遍历的节点,close用来存储已遍历的节点<br>
	 * 声明两个map,一个map为distance,key为vertex,value为double,是计算现在得到的这个vertex距离起始点的最短路径<br>
	 * 一个map为path,key为vertex,value为vertex,是出发点到结束点的最短距离的路径的最后的中转节点<br>
	 * 初始阶段,将所有节点放入open<br>
	 * distance里面先初始为doubleMax,Path先初始为vertex自己<br>
	 * 将起始点放入close,设置distance=0,path=自己,更新起始点周围的节点的距离,设置他们的distance=边的距离,path=起始点<br>
	 * 以初始节点为中心向外一层层遍历,获取离指定节点最近的子节点(遍历open中的vertex,找到distance最小的vertex)<br>
	 * 放入close并从新计算路径,直至close包含所有子节点(或者说open为空)<br>
	 * @param first
	 * @return 返回一个map为distance,key为vertex,value为double,是计算现在得到的这个vertex距离起始点的最短路径<br>
	 */
	public HashMap<Vertex<T>, Double> getSmallestDistanceDijkstra(String first){
		Set<Vertex<T>> open=new HashSet<>();
		Set<Vertex<T>> close=new HashSet<>();
		HashMap<Vertex<T>, Vertex<T>> path=new HashMap<>();
		HashMap<Vertex<T>, Double> distance=new HashMap<>();
		Set<Vertex<T>> set=getVertexSet();
		Vertex<T> firstVertex=vertexMap.get(first);
		Edge edge;
		if(firstVertex==null){
			return distance;
		}
		//初始阶段,将所有节点放入open,distance里面先初始为doubleMax,Path先初始为vertex自己
		for(Vertex<T> vertex:set){
			open.add(vertex);
			distance.put(vertex, Double.MAX_VALUE);
			path.put(vertex, vertex);
		}
		//将起始点放入close,设置distance=0,path=自己,更新起始点周围的节点的距离,设置他们的distance=边的距离,path=起始点
		open.remove(firstVertex);
		close.add(firstVertex);
		distance.put(firstVertex, 0.0);
		path.put(firstVertex, firstVertex);
		Iterator<Edge> edgeIterator=firstVertex.getEdgeIterator();
		while(edgeIterator.hasNext()){
			edge=edgeIterator.next();
			Vertex<T> endVertex=edge.getEndVertex();
			distance.put(endVertex, edge.getWeight());
			path.put(endVertex, firstVertex);
		}
		//以初始节点为中心向外一层层遍历,获取离指定节点最近的子节点(遍历open中的vertex,找到distance最小的vertex)
		//放入close并从新计算路径,直至close包含所有子节点(或者说open为空)
		while(!open.isEmpty()){
			Double minDistance=Double.MAX_VALUE;
			Vertex<T> minVertex=null;
			for(Vertex<T> vertex:open){
				if(minDistance>distance.get(vertex)){
					//遍历open中的vertex,找到distance最小的vertex
					minDistance=distance.get(vertex);
					minVertex=vertex;
				}
			}
			放入close并从新计算路径,直至close包含所有子节点(或者说open为空)
			open.remove(minVertex);
			close.add(minVertex);
			//System.out.println("加入节点:"+minVertex.getLabel());
			edgeIterator=minVertex.getEdgeIterator();
			while(edgeIterator.hasNext()){
				edge=edgeIterator.next();
				Vertex<T> endVertex=edge.getEndVertex();
				Double weight=edge.getWeight();
				//如果之前的距离>初始到minVertex+minVertex到endVertex,就替换
				if(distance.get(endVertex)>distance.get(minVertex)+weight){
					distance.put(endVertex, distance.get(minVertex)+weight);
					path.put(endVertex, minVertex);
				}												
			}			
		}
		
		for(Vertex<T> vertex:set){
			System.out.println("到顶点:"+vertex.getLabel()+
					" ,最短距离为:"+distance.get(vertex)+" ,最后中转点为:"+path.get(vertex).getLabel());							
		}
				
		return distance;
	}

测试

package datastructure.graph.adjacencymatrixgraph;

public class Main {

	public static void main(String[] args) {
		Graph<String> graph=new Graph<>(false);
		graph.addVertex("first", 0);
		graph.addVertex("second", 0);
		graph.addVertex("third", 0);
		graph.addVertex("fourth", 0);
		graph.addEdge("first", "second", 1);
		graph.addEdge("first", "third", 2);
		graph.addEdge("third", "fourth", 3);
		
		graph.addEdge("second", "third", 1);
		graph.addEdge("second", "fourth", 2);
		
		graph.printGraph();
		
		//graph.breathFirstTraversal("first");
		
		//graph.depthFirstTraversal("first");
		
		//graph.getTopuSort();
		//graph.generateMinTreePrim("first");
		
		//graph.generateMinTreeKruskal();
		
		//graph. getSmallestDistanceFloyd();
		
		graph.getSmallestDistanceDijkstra("first");
	}

}