单源最短路径问题

1.最短路径

给定一个带权有向图G=(V,E),其中每条边的权都是非负实数。另外再给定V中的一个顶点,称为。现要计算从源到其他各顶点的最短路长度。这里路的长度是指路上各边权之和。这个问题通常称为最短路径问题

最短路径在实际中有重要的应用价值。如用顶点表示城市,边表示两城市之间的道路,边上的权值表示两城市之间的距离。那么城市A到城市B连通的情况下,哪条路径距离最短呢,这样的问题可以归结为最短路径问题。

求最短路径常见的算法有Dijkstra算法和Floyd算法。本文将详细讲解Dijkstra算法的原理和实现。

2.Dijkstra算法

2.1算法简介

Dijkstra算法是由E.W.Dijkstra于1959年提出,又叫迪科斯彻算法,它应用了贪心算法思想,是目前公认的最好的求解最短路径的方法。算法解决的是带权有向图中单个源点到其他顶点的最短路径问题,所以也叫作单源最短路径算法。其主要特点是每次迭代时选择的下一个顶点是标记点之外距离源点最近的顶点。可以求出源点到其他所有点的最短路径,当然也可以指定源点和目标点,求两点之间的最短路径。其做法是迭代至目标点被标记时结束。

Dijkstra 算法的基本思路是先将与起点有边直接相连的节点到起点的距离记为对应的边的权重值,将与起点无边直接相连的节点到起点的距离记为无穷大。然后以起点为中心向外层层扩展,计算所有节点到起点的最短距离。每次新扩展到一个距离最短的点后,更新与它有边直接相邻的节点到起点的最短距离。当所有点都扩展进来后,所有节点到起点的最短距离将不会再被改变,因而保证了算法的正确性。

2.2算法思想

Dijkstra 算法的基本思路是先将与起点有边直接相连的节点到起点的距离记为对应的边的权重值,将与起点无边直接相连的节点到起点的距离记为无穷大。然后以起点为中心向外层层扩展,计算所有节点到起点的最短距离。每次新扩展到一个距离最短的点后,更新与它有边直接相邻的节点到起点的最短距离。当所有点都扩展进来后,所有节点到起点的最短距离将不会再被改变,因而保证了算法的正确性。

Dijkstra算法是解决单元最短路径问题的一个贪心算法。基本思想是:设置顶点集合S并不断地做贪心选择来扩充这个集合一个顶点属于集合S当且仅当从源到该顶点的最短路径长度已知。初始时,S中仅含源。设u是G的某一个顶点,把从源到u且中间只经过S中顶点的路径,称为从源到u的特殊路径,并用数组dist记录当前每个顶点所对应的最短特殊路径长度。Dijkstra算法每次从V-S中取出具有最短特殊路长度的顶点u,将u添加到S中,同时对数组dist做必要的修改。一旦S包含了所有V中顶点,dist就记录了从源到到其他所有顶点之间的最短路径长度。

2.3算法基本过程

Dijkstra 算法求解单源最短路径问题的基本步骤如下: 
(1)设立U 和Y两个节点集合, Y用于保存所有未被访问的节点,U 记录所有已经访问过的节点。已经被访问指的是节点已经被纳入最短路径中。 
(2)从Y中找出距离起点最近的节点,放入U中,并更新与这个节点有边直接相连的相邻节点到起始节点的最短距离。 
(3)重复步骤(2)直到Y集合为空,即从起点出发可以到达的所有顶点都在集合U中为止。

Dijkstra 算法的基本思想和求解步骤决定了Dijkstra算法只能解决最基本的在起点和终点之间求最短路径的问题,无法解决添加了其他限制条件的,如要求经过指定中间节点集的最短路径问题,Dijkstra 算法是无法直接解决的。

2.4算法实例过程描述

已有带权有向图G如下图所示,现求节点2到节点3的最短路径。这里要求节点ID从0开始并且连续编号,且边的权值大于0。后面的代码实现也是要遵循这两个前提条件的。

如果两个节点见未直接相连,则节点间的距离设为无穷大,可用一个很大的数表示。

图中红色数字表示边的ID,圆圈中的数字为节点ID,边旁边的黑色数字表示节点间边的权值。并且所有ID均不重复。

(1)初始时,标记起点2为已访问的节点(即为源),并置于集合U中,此时集合U={2},集合Y={0,1,3}。

且初始化其它节点到起点2的距离distance[N]数组,N表示图中节点数。distance[N]数组不仅需要保存其它节点到起点2的距离,也要保存起点2到达该节点的最短路径的最后一个中间节点,这里称为当前节点的前节点。如果没有前一个节点则设为-1。

此时,distance[N]数组初始化为 {distance[0]={∞,-1}, distance[1]={2,2}, distance[2]={0,-1}, distance[3]={10,2}}。标粗的表示该节点已经置于集合U中。

(2)在集合Y中找出距离起点2最短的节点,遍历数组distance[N]得节点1距离起点2最近,并将其加入集合U中。此时集合U={2,1},集合Y={0,3}。

(3)以新加入集合U的节点1为中间节点,更新起点2到其它节点之间的最短距离。

对节点0,因为节点2到节点0的距离为无穷大∞,大于起点2通过节点1到节点0的距离2+3=5,并且节点0的前屈节点变为1,所以更新distance[0]={5,1};

对节点3,同理,因为节点2到节点3的距离为10,小于节点2通过节点1到节点3的距离1+∞=∞,所以无需更新。

所以,更新完之后的distance[N]取值情况为:{distance[0]={5,1}, distance[1]={2,2}distance[2]={0,-1}, distance[3]={10,2}}。

(4)重复步骤2,继续在集合Y中寻找距离起点2最短的节点,并访问它。遍历数组distance[N]知道节点0到起点2的距离最短为5,其节点0加入集合U中。此时集合U={2,1,0},集合Y={3}。

(5)重复步骤3,以节点0为中间节点更新集合Y中节点到起点2的距离。因为distance[0].first+matrix[0][3]=5+4=9,小于distance[3].first=10,所以更新distance[3]={9,0}。

此时distance[N]取值情况为:{distance[0]={5,1}, distance[1]={2,2}distance[2]={0,-1}, distance[3]={9,0}}。

(6)重复步骤2,再集合Y中找出距离起点2最近的节点,遍历distance[N]可知节点3距离最近,并将其纳入集合U中。此时集合U={2,1,0,3},集合Y={}。

(7)重复步骤3,以节点3为中间节点更新集合Y中节点到起点2的距离,此时发现集合Y为空,过程结束。

最后我们获得了加入集合U的所有节点,因为没有节点都记录了自己的前驱节点,所以可以获得从起点到任意目的节点见的最短路径。

3.Dijkstra算法具体实现

以上面的描述为基础,编码实现Dijkstra算法。

3.1输入

(1)图的信息 
文件Graph(以逗号为分隔符的文本文件)给出图的数据,文件每行以换行符(ASCII’\n’即0x0a)为结尾。

图的数据中,每一行包含如下的信息: 
EdgeID,SourceID,DestinationID,Cost

其中,EdgeID为该有向边的索引,SourceID为该有向边的起始顶点的索引,DestinationID为该有向边的终止顶点的索引,Cost为该有向边的权重。顶点与有向边的索引均从0开始编号,这里要求连续,且保证索引不重复。

(2)起点与终点 
程序运行过程中,输入起点和终点。

3.2输出

打印输出起点至终点间最短路径顺序经过的节点,并且输出最短路径的长度,即边的权值和。

3.3相关数据结构

(1)图的存储结构 
图采用邻接矩阵存储,由图的信息构造。

(2)集合U和Y 
没有实际存储,逻辑的在图邻接矩阵对角线的bool值来表示在集合U还是在集合Y。比如邻接矩阵matrix[2][2]初始时为0,即自己到自己的距离是0。当被纳入集合U中,将其matrix[2][2]设置为1即可。

(3)distance[N] 
distance[N]记录了未被纳入最短路径的集合Y中的节点距离起点的最短距离,以及它的前驱节点。因为是二元信息组,所以采用C++的STL标准模板库中的键值对容器pair。pair< int,int>第一个元素表示节点ID,第二元素表示该节点的前驱节点。

(4)起点到其它所有节点的最短路径 
采用map< int,int>容器存储。如果再给定任意非起点的节点作为终点,即可从起点到其它所有节点的最短路径找出起点到终点的最短路径,并且根据关系矩阵求出最短路径的长度。

3.4时间复杂度

算法中构造邻接矩阵的时间复杂度是O(n2)O(n2),求最短路径部分又两层循环构成,外循环n-1次,内循环为n次,所以时间复杂度为O(n2)O(n2),因此总的时间复杂度为O(n2)O(n2)。这里的n表示图的节点数。


猜你喜欢

转载自blog.csdn.net/mx_studying/article/details/80856647
今日推荐