图论 - 单源最短路 - Dijkstra算法

       本文介绍求解图单源最短路径的Dijkstra算法的基本原理以及其正确性的证明。

1. 问题描述:       

       设一张有向带权图 G 包含若干顶点记为 v[1], v[2], ...;v[i] 到 v[j] 的边记为 e[i,j],其权值记为 w[i,j]。注意,Dijkstra算法正确性要求图中没有权值为负的边。问题为求源点 v[s] 到图中其他各顶点的最短路径长度。这个问题称为图的单源最短路问题。另外,对于无向图,可以把每条边看成两条双向边;对于无权图,可以将每条边权值均视为 1。

2. Dijkstra算法的基本思路:

       维护一个集合 S,它将到源点 v[s] 距离由小到大的各顶点依次包含进来。

       维护一个序列 d,d(v[i]) 记录只经由当前 S 中的顶点到达 v[i] 的最短距离,v[i] 为任意 S 中尚未包含的顶点。

       算法的基本流程为:

       首先,在 d 中找到最小值对应的顶点 v[m],将其加入 S。设此时 S 中已有 k 个点(包括源点本身),则 v[m] 为距离源点第 k 近的点(距离源点最近的点为它本身)。

       然后,对于 v[m] 的所有尚未加入到 S 的邻接点 v[j],更新 d(v[j]),更新更新公式为:d(v[j]) = min(d(v[j]), d(v[m])+w[m,j])。

       如此直至序列 d 为空。 

3. Dijkstra算法的正确性:

       下面用归纳法证明Dijkstra算法的正确性。 

       第 1 步:S 最初只有源点 v[s],认为源点到自身的距离为 0,则此时它是距离 S 最近的点。此时,d 中包含所有 v[s] 的邻接点,值为相应邻接边的权值。它们自然包含了所有只经由当前 S 中的顶点可以到达的非 S 内顶点的最短路径长度。

       若按照上一节介绍的算法流程进行到了第 k 步,假设以下命题成立:

(1) 此时 S 包括了距离源点 v[s] 前 k 近的 k 个顶点(包含源点自身)。

(2) 对于每个尚未包含在 S 内的顶点 v[i], d(v[i]) 记录了只经由当前 S 中的点到达 v[i] 的最短距离。

       只要证明:

(1) 按照Dijkstra算法的策略,下一步加入的顶点 v[m] 为距离源点 v[s] 第 k+1 近的点。

(2) 按照对于 d 的更新策略,d 中保存了从源点到所有不属于 {S+v[m]} 的顶点的只经由 {S+v[m]} 内的顶点的最短路径长度。那么,由归纳法,可知Dijkstra算法是正确的。

· 第(1)条的证明:

       由归纳假设,距离源点前 k 近的点已经包含在 S 中,我们只要证明按照Dijkstra算法策略选出的 v[m] = min{ d(v[i]), v[i] ∉ S } 是除 S 中的点外距离源点最近的点即可。可以由反证来证明,若存在另一个顶点 v[p] ∉ S,且从源点 v[s] 到 v[p] 的距离小于从源点 v[s] 到 v[m] 的距离。那么,这条最短路径上一定至少存在一个不在当前 S 中的顶点。因为,如果路径只经过当前 S 内的点,那么 d(v[p]) < d(v[m]),我们选择的就应该是 v[p] 而不是 v[m] 了。不妨设该路径上第一个不在 S 中的顶点为 v[q],那么有

distance(s→q) + distance(q→p) < d(v[m]),distance()表示最短距离。 

由于图中没有负权值的边,v[q] 不在当前 S 中,v[s] 到 v[q] 的路径只有当前 S 中的点,那么必有 d(v[q]) < d(v[m]),这与 d(v[m]) 是当前 d 中的最小值矛盾,故由反证法,要证明的第①条成立。 

· 第(2)条的证明:

       接下来考虑对于 d 的更新策略。S 包含了新加入的 v[m] 后,设 v[i] 为图中任一不在 S 中的顶点:

① 若 v[i] 不是 v[m] 的邻接点,那么 d(v[i]) 不用更新,因为所有只经过 S 中的点又经过 v[m] 的路径长度都不可能比只经过除 v[m] 外 S 中的点的最短路径短。因为对于这样的路径 v[s] → v[m] → v[i],一定在 v[m] 和 v[i] 之间至少还有一个 S 中的顶点(因为 v[i] 不是 v[m] 的邻接点),不妨记其为 v[j],那么这条路径的长度为 distance(s→m) + length(m→j) + length(j→i),其中,distance(s→m) 表示从 v[s] 到 v[m] 的最短距离,而 length(m→j) 表示只经由 S 中的顶点从 v[m] 到 v[j] 的最短距离。因为 v[j] 是在 v[m] 之前加入 S 的,那么一定存在一条由 v[s] 到 v[j] 不经过 v[m] 的通路,且 distance(s→j) < distance(s→m),那么经由这条通路到达 v[j] 然后再到达 v[i] 一定更短(长度为 distance(s→j) + length(j→i))。故包含 v[m] 的不是最短路径,不需要更新。

② 若 v[i] 是 v[m] 的邻接点,那么 v[s] 只经过 S 中的点到达 v[i] 的路径只有两类:一种是完全不包含 v[m],一种是 v[s] → v[m] → v[i]。对于第一种最短路径的长度就是当前的 d(v[i])。对于第二种路径,我们只需考虑 v[m] 是 v[i] 前一个点的路径,这样的路径长度为 distance(s→m) + w[m,i] = d(v[m]) + w[m,i]。因为若 v[m] 和 v[i] 间还存在某一 S 中的顶点 v[j],即路径的形式是这样的:v[s] → v[m] → v[j] → v[i],那么该路径的长度为 distance(s→m) + length(m→j) + length(j→i) > distance(s→j) + length(j→i),不等式右侧是一条不包含 v[m] 的路径长度。因此,对于 v[m] 的某个不在 S 中的邻接点 v[i],只要更新 d(v[i]) = min(d(v[i]), d(v[m])+w[m,i]),即可保证此时 d(v[i]) 为从 v[s] 开始只经过新加入了 v[m] 的 S 中的顶点到达 v[i] 的最短路径长度。

       综上,我们用归纳法证明了Dijkstra算法的正确性。

4. 实现技巧:

       若用线性结构维护 d,那么虽然更新 d(v[i]) 可以常数时间 O(1) 完成,但是查找最小值需要线性时间 O(|V|),|V|为图中顶点的个数。这样,Dijkstra算法的复杂度为 O(|V|2)。

       可以用一个堆(优先队列)来维护 d,这样更新和查找都可以在O(log|V|)时间内完成。此时,Dijkstra算法的复杂度为 O(|E|log|V|)(最坏的情况,每条边都要更新 d)。其中,|E|为图中边的数量。

猜你喜欢

转载自www.cnblogs.com/fyqq0403/p/10582280.html
今日推荐