图论_单源最短路径_Dijkstra

来源:互联网 发布:linux cat 编辑:程序博客网 时间:2024/05/29 11:26

给定有向赋权图 G=(V,E) , 图中的每一条边都具有非负长度,其中有一个顶点 u 称之为源顶点 。 所谓的单源点最短路径问题,就是确定由源点 u 到

其他的所有顶点的距离。 在这个地方我们将从顶点 x 到顶点 u 之间的距离,定义为从 x 到 u 最短路径的长度。通常情况在求解最短路径的时候使用

的算法是 Dijkstra 算法, 是基于贪婪算法的思想的。通常的情况下,我们都是使用 Dijkstra 算法来计算两个顶点之间的最短路径长度的, 但是Djkstra

的使用范围远远不止这些,我们还可以使用 Dijkstra 算法来计算从某一个顶点 x 到图中的所有的顶点的距离。


本篇文章主要分为三个部分,分别对应三个实现了 Dijkstra 算法的代码,最后对算法进行时间复杂度的分析。

三段代码实现分别对应的是 1. 邻接矩阵实现图存储数据结构 2.邻接表作为图存储结构 3. stl + 队列+邻接表 实现图的存储结构


1. 基于邻接矩阵的图存储方式实现 Dijkstra 算法


为了求得这些最短路径, Dijkstra 提出按照路径长度的递增次序,逐步产生最短路径的算法。

首先在所有的有向边中选出长度最短的边,在参照它求出长度次端的一条最短路径,以此类推,

直到从原点 V0 到其他的各个顶点的最短路径全部求出来为止 。


为了更详细的进行说明, 请参看下图。




图 1.1


在上图中,如果想要求出顶点 1 到其他各个顶点的最短距离是多少可以按照下面的步骤进行思考,

思考之后归纳总结出算法的一般步骤


1. 首先可以知道, 源点 1 到其他直接相邻的顶点中,最短路径距离为 d(1,4)= 3 ,所以对于顶点 4 到源点 1 的最短路径确定

,但是同样也可以确定局部最短路径<这里的局部最短路指的是,各个顶点根据局部条件所确定的最短路径,

同时很有可能由于后面的求得全局最短路径顶点的增加而变更> ,同时可以得知顶点3,5 到源顶点 1 的局部最短路径 

为d(1,3) = 9  d(1,5) = 4 ,而对于那些暂时对源点不可达的顶点,我们为了后续的更新操作可以将这些顶点与源点之间的最短距离设为无穷大。

(这个最短路径距离指的当然是局部的最短,而非全局最短)

2. 顶点 4 的最短路径求出来之后, 源顶点到其他各个顶点的局部最短路径距离也会随着顶点 4 最短路径的确定而进行调整。

比如 d( 1,2 ) 的数值从原先的无穷大,变更到了 d(1,4) + d(4,2) = 3+2 = 5  。 d( 1, 5 ) = d(1,4)+ d(4,5) = 3+1 = 4

所以此次的最短路径为 d(1,5)  = 4 <这里图中的数据设定有点问题,使得路径距离(1, 5) = (1,4,5) >


3. 随着顶点 5 的最短路径的确定,其他顶点的局部距离也会随之更新 , (1 , 4 , 5 )是已经确定了全局最短路径的顶点,

(2, 3 )是没有确定全局最短路径的顶点,仅仅确定的是局部最短路径。在这一步可以更新 d( 1,3 ) = d(1,5) +d(5,3) = 6 

d(1,2) = d(1,4) +d(4,2) = 5 , 所以 顶点 2 的局部最短路径即为此次的全局最短路径,所以截止到目前 集合 {1, 4 , 5 , 2 } 顶点已经确定了全局最短路径

接下来是顶点 3 , d( 1,3) = 6 为源点 1  到 顶点3 的最短路径长度。


Dijkstra 算法实现的步骤为:


1.首先设定两个集合,T 和 S 

    a. S中存放的是已找到全局最短路径的顶点, 运行之前要对集合S进行初始化,S初始化的时候只有一个元素顶点,就是最短路径中的源点

   b.  T中存放的是还没有找到全局最短路径的顶点

2. 在集合 T中选取一条当前长度最短的一条路径 ( v0...vk) , 从而可以将顶点 vk 从集合T中进行移除,然后加入到集合 S 中,然后修改 v0 源点到

集合 T中所有顶点的局部最短路径的长度,在借助于加入的顶点 vk 的转折下 ; 然后不断地重复这个步骤直到集合 T 为空为止 。


Dijkstra 算法的原理是: 可以通过推导来证明, v0 到 T中顶点 vk 的最短路径,一种是 v0 到 vk 之间的直接距离, 另一种就是 v0 经过 S集合中的

某一个顶点进行转折之后在到达 vk 之间的路径的长度。


好了, 既然原理我们已经知道了,下面就来看看算法是如何通过代码来实现的吧 ~


在Dijkstra算法里面,为了求源点 v0 到其他各个顶点 vi 的最短路径和路径的长度,一共需要设置 3 个数组变量:


a , dist[n] : dist[i] 表示的是当前找到的从源点 v0 到终点 vi 的最短路径的长度,在初始化的时候, dist[i] 为

Edge[v0][i] , 即为用来存放图的邻接矩阵中的数据,对于邻接矩阵 Edge[v0] [i] 是这样的:

如果 i== v0 的话, 那么表示的是源点 v0 到自身的距离,它的数值是为 0 的,

如果在图中顶点 i 与源顶点 v0 并不是直接可达的话,那么它的数值,即 Edge[v0][i] = dist[i] = MAX ,即无穷大。

如果,反之,顶点 i 与源顶点 v0 是直接可达的话, 那么边 (v0 , i ) = d = Edge[v0] [i] = dist[i] 


b.  S[n] : 这个数组是用来表示那些顶点是属于已经确定全局最短路径的,即那些顶点是属于上述的 S 集合的,

S[i] = 1  用来说明顶点 i 的全局最短路径已经确定了,并且顶点 i 在集合 S 中,

S[i] = 0 用来说明顶点 i 的全局路径还没有确定, 顶点 i 在集合 T 中。

在算法刚刚开始运行的时候, S[v0] = 1 ,因为只有 v0 是确定全局最短路径的, 为 0 ,为到其自身的距离

在算法结束的时候,所有顶点的最短路径已经找到, S[ v0 ....vn] = 1 ;

通常也是通过这个变量来判断算法是否结束。


c. path[ n ] : path[i] 可以用来表示源顶点 v0 到顶点 vi 在全局的最短路径上顶点 vi 的前一个顶点序号。

采用的是“倒向追踪” 的方法,这样的话就可以确定v0 到顶点 vi 的全局阻断路径上面每个顶点的序号。


下面是算法的主体:


1. 在数组 dist[n] 的里面查找 S[i] ! = 1 ,并且 dist[ i ] = min( dist[0..n] ) 的顶点 u 

也就是在集合 T 中找到dist [i] 数值最小的顶点 ,找到之后将其标定为 u

2. 将S[u] 置位为 1 ,表示的是顶点 u 已经从集合 T 中被移除,并被安放到集合 S 中。

3. 访问与顶点 u 直接的所有的位于集合 T 中的图中顶点,如果满足条件

dist[u] + Edge[u] [i] < dist[ i ] && !S[i]  (0<=i <= n) n 为图中的顶点数目

那么就将顶点 i 所对应的到源顶点 v0 的距离进行更新,并将顶点 i 的前趋顶点修改为顶点 u 

dist[i] = dist[u]+Edge[u][i] ;

path[i] = u ;

不断地重复上述三步操作,直至集合 T 为空,或是集合 S 中的全部元素数值为 1 。


 

下面是整个算法的代码实现:


假设数据输入的时候全部采用如下的格式进行输入: 首先输入顶点的个数是 n  , 然后输入每条边上的数据。

每条边的数据格式为 : u v w , 着三个数值分别表示的是边的起点、终点和边上的权值。 顶点序号从 0  开始计算。

最后输入 -1 -1 -1 ,来表示输入数据的结束。



#include <stdio.h>#include <string.h>#include <vector> #define INF 1000000 #define MAXN 20 int n ; int Edge[MAXN][MAXN] ;int S[MAXN] ;int dist[MAXN] ;int path[MAXN] ;void Dijkstra( int v0 ){int i , j , k ;for ( i = 0 ; i < n ; i++ ){dist[i] = Edge[v0][i] ;S[i] = 0 ;if ( i != v0 && dist[i] < INF )path[i] = v0 ;elsepath[i] = -1 ;}S[v0] = 0 ;dist[v0] = 0 ;for( i = 0 ; i < n-1 ; i++ ){int min = INF , u= v0 ;for ( j = 0 ; j < n ; j++ ){if (!S[j] && dist[j] < min ){u = j ;min = dist[j] ;}}S[u] = 1 ;for ( k = 0 ; k < n ; k++ ){if (!S[k] && Edge[u][k] < INF && dist[u]+Edge[u][k] < dist[k] ){dist[k] = dist[u]+Edge[u][k] ;path[k] = u ;}}}}int main (){int i , j ;int u , v , w ;scanf("%d", &n ) ;while( 1 ){scanf("%d%d%d", &u, &v , &w ) ;if(u==-1 && v==-1&& w==-1)break ;Edge[u][v] = w ;}for( i = 0 ; i < n ; i++ ){for ( j = 0 ; j < n ; j++ ){if ( i==j ) Edge[i][j] = 0 ;else if ( Edge[i][j] == 0 )Edge[i][j] = INF ;}}Dijkstra(0) ;int shortest[MAXN] ;for ( i = 1 ; i < n ; i++ ){printf("%d\t", dist[i] ) ;memset( shortest , 0 , sizeof( shortest)) ;int k = 0 ; shortest[k] = i ;while( path[shortest[k]] != 0 ){k++ ;shortest[k] = path[shortest[k-1]] ;}k++ ;shortest[k] = 0 ;for( j = k ; j > 0 ; j-- )printf("%d->" , shortest[j] ) ;printf("%d\n" , shortest[0] ) ;}system("pause") ;return 0 ;}


另外两个部分,有时间再写, 现在要看Thrift 多线程那个地方了~



















0 0