引言:

DAG上的最短路可以用dp解得,容易得到状态转移方程。但如果图中可以有环,就要使用其他算法了。这里先考虑边权均为正的最短路问题。

 

Contents:

一、 普通的dijkatra实现  O(n2)

二、采用邻接表

三、 基于优先队列的Dijkstra O(mlogn)


 给出样例问题(图片引自Tanky Woo):

对下图中的有向图,用Dijkstra算法计算从任意源顶点到其它顶点间最短路径。

输入:

6
7
1 2 10
1 4 30
1 5 100
2 3 50
3 5 10
4 3 20
4 5 60
1
2
3
4
5
输出:
No.1->No.5 :60
No.2->No.5 :60
No.3->No.5 :10
No.4->No.5 :30
No.5->No.5 :0

接下来就此问题,分别介绍几种复杂度不同的实现方法。


一、 普通的dijkatra实现

首先来谈谈这个算法。记st为起点,dist[i]为结点 i 到st的最短路径长。

初始化dist[st] = 0; 其余dist为INF(类似maxint)。当前结点 u = st。

从当前点u出发遍历邻点,更新dist[v] = min{ dist[v], dist[u]+w[u][v]};  ----------这里有点像状态转移

设一个集合S,总是把当前不在S中的结点的最小dist对应的点加入S中。

当所有点都在集合S中时,完毕。此时dist数组更新完毕,即从st到图上任意结点的最短路长都已经算出。

 

理解:关键在于集合S,每次取当前最小的dist。可见取到S中的点的dist都是已经更新完毕的了,即已算出的。

可以用反证明法想想,如果某次dist[i]是最小的dist,但是st到 i 还存在经过 u' 的更短的路,则

dist[u']必然 小于dist[i],即u'会比 i 先成为当前点,那时dist[i]就会在u'作为当期点的时候被更新为

dist[u']+w[u'][i]了。 所以,dist[i]必是st到 i 的最短路长。

 

每次取一个元素到S中,所以循环n-1次,每次循环时,求最小dist(遍历n个结点)和更新相邻dist值(若采用邻接矩阵)都是O(n)的,所以时间复杂度为O(n^2)。

 

这里附上其他说法,本质一样,链接如下:

http://www.nocow.cn/index.php/Dijkstra

http://www.wutianqi.com/?p=1890

http://baike.baidu.com/view/7839.htm

 

下面给出详细注释的实现代码:

注:对于上面的G和w数组,可以省掉一个G,只要在w初始化时初始为INF,就可以隐式地表示边不存在。

也不需要判断相邻了。

 

 二、采用邻接表

 注意到上面找相邻点时都要循环n次,故这里采用邻接表优化。

同样也可以用verctor<int> G[MAXN];

代码① verctor<int> G[MAXN]  :

 

代码② 数组实现邻接表(+二叉堆,具体下面会解释)(by Rujia Liu):

 

 

三、 基于优先队列的Dijkstra

上面说到二叉堆, 其实就是使用二叉堆(Binary Heap)来保存没有扩展过的点的距离并维护其最小值,

并在访问每条边的时候更新。O(mlogn)。

而这里用的是优先队列,其实实质差不多,效果一样。上面的Lrj那个用的其实也是优先队列。

step1

为了优化“找出不在S中的最小dist”,采用优先队列(priority_queue),在优先队列中,元素是按优先级排列的,

所以不再是front,而是top,pop删除的也是优先级最高的元素。 而它默认是利用元素自身“小于”操作符来定义优先级的,

所以默认是不断pop最大的数。在这里,要用great<int>表示大于运算符,或者重载元素的小于操作,来使得出队的总是最小的dist。

step2

还有一个问题,就是我们要得到的是最小dist的结点标号,所以这里用到STL的pair,把dist[i]跟 i 捆绑在一起入队,因为pair定义是

先比较第一维,相当才比较第二维,所以组合方式是(dist[i], i) 而不是 (i, dist[i])。

 

给出代码,可以看成是代码(二①)的优化: