图的最短路径算法-java版

目录

最短路径

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版_第1张图片

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

图的最短路径算法-java版_第2张图片

根据以往的经验,如果要让任意两个顶点(假设从顶点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版_第3张图片

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

图的最短路径算法-java版_第4张图片

图的最短路径算法-java版_第5张图片

图的最短路径算法-java版_第6张图片

图的最短路径算法-java版_第7张图片

初始状态: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实现

图的基础代码

https://blog.csdn.net/xushiyu1996818/article/details/90373591

Floyd算法


	/**使用Floyd(弗洛伊德)算法,返回所有节点间的最短距离
* 设置两个map
* 第一个 result,key为出发点,value是map,这个map的key是结束点,value是出发点到结束点的最短距离
* 第二个 path,key为出发点,value是map,这个map的key是结束点,value是出发点到结束点的最短距离的路径的最后的中转节点
* 一开始,result里的value为maxDouble(到自己的value为0),path里的value是结束点
* 然后,用图里的所有的边对result做初始化,当不经过任意第三节点时,其最短路径为初始路径,只对图里先有的只经过两点的边,对result里的value更新
*
* 进行循环 * 当只允许经过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, HashMap, Double>> getSmallestDistanceFloyd(){ //第一个 result,key为出发点,value是map,这个map的key是结束点,value是出发点到结束点的最短距离 Map, HashMap, Double>> result=new HashMap<>(); //第二个 path,key为出发点,value是map,这个map的key是结束点,value是出发点到结束点的最短距离的路径的最后的中转节点 Map, HashMap, Vertex>> path=new HashMap<>(); Set> vertexSet=getVertexSet(); Vertex vertex; Edge edge; for(Vertex begin:vertexSet){ HashMap, Double> distanceMap=new HashMap<>(); HashMap, Vertex> pathMap=new HashMap<>(); for(Vertex 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 begin:vertexSet){ HashMap, Double> distanceMap=result.get(begin); Iterator edgeIterator=begin.getEdgeIterator(); while(edgeIterator.hasNext()){ edge=edgeIterator.next(); //用图里的所有的边对result做初始化,当不经过任意第三节点时,其最短路径为初始路径,只对图里先有的只经过两点的边,对result里的value更新 distanceMap.put(edge.getEndVertex(), edge.getWeight()); } result.put(begin, distanceMap); } for(Vertex mid:vertexSet){ for(Vertex begin:vertexSet){ HashMap, Double> distanceMap=result.get(begin); HashMap, Vertex> pathMap=path.get(begin); for(Vertex 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 begin:vertexSet){ HashMap, Double> distanceMap=result.get(begin); HashMap, Vertex> pathMap=path.get(begin); for(Vertex end:vertexSet){ System.out.println("从顶点:"+begin.getLabel()+" ,到顶点:"+end.getLabel()+ " ,最短距离为:"+distanceMap.get(end)+" ,最后中转点为:"+pathMap.get(end).getLabel()); } } return result; }

dijkstra算法

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

}

你可能感兴趣的:(数据结构,数据结构-图)