注:本文为 “Dijkstra 算法” 相关合集。
图片清晰度受引文原图所限。
略作重排,未整理去重。
如有内容异常,请看原文。
Dijkstra’s Shortest Path Algorithm - A Detailed and Visual Introduction
~
Humilitas 2021年1月22日
通过这篇文章图文解释来理解 Dijkstra 算法背后的工作原理。
- 图的基本概念。
- Dijkstra 算法的使用场景。
- Dijkstra 算法的工作原理。
🔹 “图”简介 ✨
基本概念
图是一种用来表示元素对之间的“连接”的数据结构。
- 这些元素称为节点,它们表示现实生活中的对象、人或实体。
- 节点之间的连接称为边。
下面是“图”的图形表示:

彩色的圆圈表示节点,圆圈之间的连线表示边。
💡 提示: 如果两个节点之间有连线表示它们是互相连接的。
应用
图可以应用到现实世界中的场景,例如:可以用来建模交通运输网络,节点表示发送或接收物资的站点,边表示站点之间的路线(如下图所示)。

用图表示交通运输网络
图的类型
图可以是:
- 无向的: 在互相连接的节点之间可以以任意方向移动。
- 有向的: 在互相连接的节点之间只能以特定的方向移动。使用带单向箭头的线来表示有向边。

💡 提示: 本文中使用的是无向图
权重图
权重图的边是带有“权重”的,边的权重可以表示距离、时间或其他能够以节点之间的“连接”表示的概念。
下面的权重图中,每个边旁边都有一个蓝色数字表示其权重。

💡 提示: 这些权重对于 Dijkstra 算法而言是必不可少的,稍后会解释其原因。
🔸 Dijkstra 算法简介
了解了图的基本概念,我们开始研究这个出色的算法。
- 算法目标和使用场景
- 历史
- 基础知识
- 必要条件
算法目标和使用场景
使用 Dijkstra 算法,可以寻找图中节点之间的最短路径。特别是,可以在图中寻找一个节点(称为“源节点”)到所有其它节点的最短路径,生成一个最短路径树。
GPS 设备使用这个算法来寻找当前位置到目标位置的最短路径。Dijkstra 算法被广泛应用在工业上,尤其是需要建模网络的领域。
历史
荷兰杰出计算机科学家、软件工程师 Dr. Edsger W. Dijkstra 创建并发布了这个算法。
1959 年,他发表了一篇 3 页的文章《A note on two problems in connexion with graphs》来介绍他的新算法。

1994 年,Edsger Dijkstra 博士 在 ETH Zurich(Andreas F. Borchert 摄)
在 2001 年的一次采访中,Dijkstra 博士透露了他设计这个算法的起因和过程:
从 Rotterdam 到 Groningen 的最短路线是什么?
我花了大概 20 分钟时间设计了这个寻找最短路径的算法。一天早上我正和我年轻的未婚妻在 Amsterdam 逛街,觉得有点累了,我们就坐在咖啡厅的露台上喝了一杯咖啡,我在想是否能够解决这个问题,然后,我设计出了这个最短路径算法。
我说过,这是一个 20 分钟的设计。事实上,三年之后的 1959 年它才被发布,现在看来依然很不错,其原因之一是我当时设计的时候没有纸和笔,从而不得不极力避免所有可避免的复杂性。
最终,令我惊讶的是,这个算法成为了我成名的基石之一。
——引自文章《An interview with Edsger W. Dijkstra》.
⭐ 难以置信,对吧? 仅仅 20 分钟的时间,Dijkstra 博士设计出了位列计算机科学史上最著名的算法之一的 Dijkstra 算法。
Dijkstra 算法的基础知识
- Dijkstra 算法从指定的节点(源节点)出发,寻找它与图中所有其它节点之间的最短路径。
- Dijkstra 算法会记录当前已知的最短路径,并在寻找到更短的路径时更新。
- 一旦找到源节点与其他节点之间的最短路径,那个节点会被标记为“已访问”并添加到路径中。
- 重复寻找过程,直到图中所有节点都已经添加到路径中。这样,就可以得到从源节点出发访问所有其他节点的最短路径方案。
必要条件
Dijkstra 只能用在权重为正的图中,因为计算过程中需要将边的权重相加来寻找最短路径。
如果图中有负权重的边,这个算法就无法正常工作。一旦一个节点被标记为“已访问”,当前访问它的路径就被标记为访问它的最短路径。如果存在负权重,则可能在之后的计算中得到总权重更小的路径,从而影响之前的结果(译注:即可能出现多绕路反而路线更短的情况,不合实际)。
🔹 Dijkstra 算法示例
理解了算法概念之后,通过逐步的示例来了解一下它背后的工作原理。
假设有下面这个图:

Dijkstra 算法将会寻找出图中节点 0 到所有其他节点的最短路径。
💡 提示: 在这个图中,我们假定两个节点之间的权重表示它们之间的距离。
我们将会得到节点 0 到节点 1、节点 0 到节点 2、节点 0 到 节点 3……(以此类推)的最短路径。
初始的距离列表如下:
- 源节点到它自身的距离为
0。示例中的源节点定为节点0,不过你也可以选择任意其它节点作为源节点。 - 源节点到其它节点的距离还没有确定,所以先标记为无穷大。

还有一个列表用来记录哪些节点未被访问(即尚未被包含在路径中):

💡 提示: 记住,当所有节点都被添加到路径中时,算法的计算过程就完成了。
我们选择了从节点 0 出发,可以直接将它标记为“已访问”,同样的,在未访问节点列表中把它划掉,并在图中给它加上红色的边框:


现在需要检查节点 0 到相邻节点的距离,两个相邻节点分别是节点 1 和节点 2(注意看红色的边):

💡 提示: 这并不是说立即把这两个相邻节点加入到最短路径中。在把一个节点加入到最短路径之前,需要确认是否已经寻找到了访问它的最短路径。现在只是在对可选方案做初步检查。
更新节点 0 到节点 1、节点 0 到节点 2 的距离为它们之间的边的权重,分别为 2 和 6:

更新了到相邻节点的距离之后:
- 根据已知的距离列表选择距离源节点最近的节点。
- 将它标记为“已访问”。
- 将它添加到路径中。
查看距离列表,发现节点 1 到源节点的距离是最短的(距离为 2),所以把它加入到路径中。
在图中,以红色边来表示:

在距离列表中用红色方块标记这个节点,表明它是“已访问”的、已经寻找到了访问这个节点的最短路径:

在未访问节点列表中将它划掉:

现在分析新的相邻节点,寻找访问它们的最短路径。只需要分析已经在最短路径(标记为红色边)中的节点的相邻节点。
节点 2 和节点 3 都是最短路径包含的节点的相邻节点,因为它们分别与节点 0 和节点 1 直接相连,如下图所示。下一步将要分析这两个节点。

之前已经计算过源节点到节点 2 的距离,并记录在了列表中,所以不用更新。这次只需要更新源节点到新的相邻节点(节点 3)的距离:

这个距离是 7,来看看为什么。
为了计算源节点到另一个节点(这里指节点 3)的距离,需要把访问该节点的最短路径的所有边权重相加:
- 对于节点
3: 将构成路径0 -> 1 -> 3的所有边权重相加,得到总距离为 7(0 -> 1距离为 2,1 -> 3距离为 5)。

现在得到了到相邻节点的距离,需要选择一个节点添加到路径中。我们必须选择一个已知到源节点距离最短的未访问节点。
从距离列表中可以看出,距离为 6 的节点 2 就是我们的选择:

在图中为它加上红色边框,并将路径上的边标记为红色:

在距离列表中用红色方块把它标记为“已访问”,在“未访问”节点列表中把它划掉:


重复前面的步骤,寻找源节点到新的相邻节点节点 3 的最短路径。
可以看到,有两种可选的路径:0 -> 1 -> 3 或 0 -> 2 -> 3。一起看看我们是如何确定最短路径的。

节点 3 在之前已经有了一个距离记录(距离为 7,参阅下表),这个距离是之前步骤中由路径 0 -> 1 -> 3 的两个边权重(分别为 5 和 2)相加得到的。
不过现在有了一个新的可选路径:0 -> 2 -> 3,它途经权重分别为 6 和 8 的两条边 0 -> 2 和 2 -> 3,总距离为 14。

显然,第一个路径的距离更短(7 vs. 14),所以选择第一个路径 0 -> 1 -> 3。只有在新的路径距离更短的情况下,才会更新距离列表。
因此,使用第一种方案 0 -> 1 -> 3,将节点添加到路径中。

把这个节点标记为“已访问”,在“未访问”节点列表中把它划掉:


重复前面的过程。
检查尚未访问的相邻节点:节点 4 和节点 5,因为它们是节点 3 的相邻节点。

更新它们到源节点的距离,尝试寻找更短的路径:
- 对于节点
4: 路径是0 -> 1 -> 3 -> 4,距离为 17。 - 对于节点
5: 路径是0 -> 1 -> 3 -> 5,距离为 22。
💡 提示: 注意我们只能从最短路径(红色边)上进行扩展,而不能途经未被包含在最短路径中的边(例如,不能构造经过边 2 -> 3 的路径)。

现在需要选择将哪个未访问节点标记为“已访问”,这里选择节点 4,因为在距离列表中它的距离最短。在图中做标记:

在距离列表中用红色方块将它标记为“已访问”:

在“未访问”节点列表中把它划掉:

再次重复前面的过程。检查相邻节点:节点 5 和节点 6。分析每一种从已访问节点到它们之间的可能路径方案。

对于节点 5:
- 第一种选择是路径
0 -> 1 -> 3 -> 5,到源节点的距离为 22(2 + 5 + 15),前面的步骤已经记录了这个距离。 - 第二种选择是路径
0 -> 1 -> 3 -> 4 -> 5,到源节点的距离为 23(2 + 5 + 10 + 6)。
显然,第一个路径距离更短,为节点 5 选择第一种方案。
对于节点 6:
- 可选的路径是
0 -> 1 -> 3 -> 4 -> 6,到源节点的距离为 19(2 + 5 + 10 + 2)。

把距离最短(当前已知)的节点 6 标记为“已访问”。

在“未访问”节点列表中把它划掉:

现在得到了如下路径(标记为红色):

现在只剩下一个节点 5 还没被访问了,看看我们要如何把它添加到路径中。
从已经添加到路径中的节点出发,有三种不同的路径可以访问节点 5:
- 第一种选择:
0 -> 1 -> 3 -> 5,总距离为 22(2 + 5 + 15)。 - 第二种选择:
0 -> 1 -> 3 -> 4 -> 5,总距离为 23(2 + 5 + 10 + 6)。 - 第三种选择:
0 -> 1 -> 3 -> 4 -> 6 -> 5,总距离为 25(2 + 5 + 10 + 2 + 6)。

选择总距离为 22 的最短路径:0 -> 1 -> 3 -> 5。

把这个节点标记为“已访问”,并在“未访问”节点列表中把它划掉:


瞧! 我们得到了从节点 0 到图中每个节点的最短路径。

图中,标记为红色的边表示最短路径:连接节点 0 和目标节点的红色边即为从源节点出发访问目标节点的最短路径。
例如,想要从节点 0 出发访问节点 6,连接它们的红色边就是最短路径,跟着走就行了。
🔸 总结
- 图可以用来建模对象、人或实体之间的连接。它有两个关键要素:节点和边,节点表示对象,边表示对象之间的连接。
- Dijkstra 算法能够寻找出图中指定节点(“源节点”)到所有其他节点的最短路径。
- Dijkstra 算法利用边的权重来做计算,寻找源节点到所有其他节点的总距离最短(总权重最小)的路径。
现在你已经理解了 Dijkstra 算法背后的工作原理了。
Dijkstra:Why numbering should start at zero
~

~

~

~

~

~

~
最短路径之迪杰斯特拉算法(Dijkstra)——贪心算法
Wayward:)
迪杰斯特拉(Dijkstra)算法主要是针对没有负值的有向图,求解其中的单一起点到其他顶点的最短路径算法。本文主要总结迪杰斯特拉(Dijkstra)算法的原理和算法流程,最后通过程序实现在一个带权值的有向图中,选定某一个起点,求解到达其它节点的最短路径。
举个反例:

当我们使用迪杰斯特拉算法的时候,第一次更新路径长度为1的时候会确定
V
1
−
−
>
V
2
V_{1}-->V_{2}
V1−−>V2 的最短路径,但是显然这不是最短的。
1.算法原理
迪杰斯特拉(Dijkstra)算法是一个按照路径长度递增的次序产生最短路径的算法。主要特点是以起始点为中心按照长度递增往外层层扩展(广度优先搜索的思想),直到扩展到终点为止。
选择最短路径顶点的时候依照的是实现定义好的贪心准则,所以该算法也是贪心算法的一种。
首先,我们引进一个辅助数组 Distance,它的每个分量
D
[
i
]
D[i]
D[i] 表示当前所找到的从起始点v0到每个终点vi的最短路径长度。
- 它的初态为:若
v
0
v_{0}
v0到
v
i
v_{i}
vi有弧,则
D
[
i
]
D[i]
D[i]为弧上的权值;
否则,置 D [ i ] D[i] D[i]为 ∞ ∞ ∞。
显然,此时长度为
D [ j ] = M i n { D [ i ] ∣ v i ∈ V } D[j]=Min\{D[i]|v_{i}∈V\} D[j]=Min{D[i]∣vi∈V}
的路径就是从 v 0 v_{0} v0出发长度最短(为1)的一条路径,此时路径为 ( v 0 , v i ) (v_{0},v_{i}) (v0,vi)。 - 那么下一条长度次短的最短路径是哪一条?
假设该次短路径的终点是 v k v_{k} vk,则这条路径要么是 ( v 0 , v k ) (v_{0},v_{k}) (v0,vk) ,要么是 ( v 0 , v j , v k ) (v_{0},v_{j},v_{k}) (v0,vj,vk) ,即两者最短的那一条。
其中, v j ∈ S v_{j}∈S vj∈S,S为当前已求得最短路径的终点的集合; V V V 为待求解的最短路径的顶点集合。
问题一:
为什么下一条次短路径要这样选择?(贪心准则的正确性)
不失一般性,假设
S
S
S 为已经求得最短路径的顶点集合,下一条最短路径的终点是
x
x
x,利用反证法,若此路径有一条顶点不在
S
S
S 中,则说明存在一条终点不在S而长度比此路径长度短的路径,但是这显然是矛盾的,因为我们是按照路径长度递增的次序来产生最短路径的。
所以,下一条长度次短的路径长度应该是:
D [ j ] = M i n { D [ i ] ∣ v i ∈ V − S } D[j] = Min\{D[i] | v_{i}∈V-S\} D[j]=Min{D[i]∣vi∈V−S}
其中, D i D_{i} Di 或者是弧 ( v 0 , v i ) (v_{0},v_{i}) (v0,vi) 上的权值,或者是 D [ k ] ( v k ∈ S ) D[k](v_{k}∈S) D[k](vk∈S) 和弧 ( v k , v i ) (v_{k},v_{i}) (vk,vi) 上的权值之和。
问题二:
这样产生的路径是否就是S中对应顶点的最短路径?
- 假设以当前长度来看,假设当前长度对应的顶点v,若长度再增加,是不可能产生权值更短的路径的(对于v来说),因为长度增加的路径肯定包含此时长度的子路径,而此时选择的路径是该长度下最短的路径;
- 以一个顶点来看,源点 v 0 v_{0} v0 到一顶点 v k v_{k} vk 的最短路径的子路径 v 0 v_{0} v0 到 v i v_{i} vi 肯定也是该长度对应的最短路径(即 v 0 v_{0} v0 到 v i v_{i} vi 的最短路径),所以这也是我们为什么在S中选择中转结点的原因。
- 其中很大一部分原因是最短路径的最优子结构,即全局的最优解( v 0 v_{0} v0 到 v k v_{k} vk )包含局部的最优解( v 0 v_{0} v0 到 v i v_{i} vi ),且全局的最优解能够通过局部最优解逐步构造。
这也是迪杰斯特拉算法的贪心策略能取得最优解的原因。
2.算法流程
根据上面的算法思想,我们有下面算法的实现流程:
假设用带权值的邻接矩阵 a r c s arcs arcs 来表示带权的有向图, a r c s [ i ] [ j ] arcs[i][j] arcs[i][j] 表示弧 < v i , v j > <v_{i},v_{j}> <vi,vj> 上的权值。
若 < v i , v j > <v_{i},v_{j}> <vi,vj> 不存在,则置 a r c s [ i ] [ j ] arcs[i][j] arcs[i][j] 为 ∞ ∞ ∞(计算机上允许的最大值)
-
- 初始化:已求顶点集
S
S
S,初始状态为空集;从
v
0
v_{0}
v0 出发到图上其余各顶点(终点)
v
i
v_{i}
vi 可能到达的最短路径长度初值:
~
D i s t a n c e [ i ] = a r c s [ l o c a t e ( G , v 0 ) ] [ i ] v i ∈ V Distance[i] = arcs[locate(G,v_{0})][i] \ v_{i}∈V Distance[i]=arcs[locate(G,v0)][i] vi∈V
- 初始化:已求顶点集
S
S
S,初始状态为空集;从
v
0
v_{0}
v0 出发到图上其余各顶点(终点)
v
i
v_{i}
vi 可能到达的最短路径长度初值:
-
- 选择结点 v j v_{j} vj,使得
~
D i s t a n c e [ j ] = M i n { D i s t a n c e [ i ] ∣ v i ∈ V − S } Distance[j] = Min\{Distance[i] \ | \ v_{i}∈V -S \} Distance[j]=Min{Distance[i] ∣ vi∈V−S}
v j v_{j} vj 就是当前求的从 v 0 v_{0} v0 出发的最短路径的终点。令
S = S ∪ j S = S \cup {j} S=S∪j
-
- 修改 从 v 0 v_{0} v0 出发到集合 V − S V-S V−S 上的任一顶点 v k v_{k} vk 可达的最短路径长度。
~
若 D i t a n c e [ j ] + a r c s [ j ] [ k ] < D i s t a n c e [ k ] Ditance[j] + arcs[j][k] < Distance[k] Ditance[j]+arcs[j][k]<Distance[k]
则修改 D i s t a n c e [ k ] = D i t a n c e [ j ] + a r c s [ j ] [ k ] Distance[k] = Ditance[j] + arcs[j][k] Distance[k]=Ditance[j]+arcs[j][k]
-
- 重复 (2)(3)共 n − 1 n-1 n−1 次,求的 v 0 v_{0} v0 到其余个顶点的最短路径是依路径长度递增的序列。
其中,最短路径的存储可以采用一个path数组,存储到某结点的最短路径中该结点的前驱结点在图中的位置(起点
v
0
v_{0}
v0 的前驱可以设成
−
1
-1
−1 )。
即若有下面这样一个带全有向图:

其邻接矩阵如下:
[ g r a p h v 0 v 1 v 2 v 3 v 4 v 5 v 0 ∞ ∞ 10 ∞ 30 100 v 1 ∞ ∞ 5 ∞ ∞ ∞ v 2 ∞ ∞ ∞ 50 ∞ ∞ v 3 ∞ ∞ ∞ ∞ ∞ 10 v 4 ∞ ∞ ∞ 20 ∞ 60 v 5 ∞ ∞ ∞ ∞ ∞ ∞ ] \left[ \begin{matrix} graph & {{v}_{0}} & {{v}_{1}} & {{v}_{2}} & {{v}_{3}} & {{v}_{4}} & {{v}_{5}} \\ {{v}_{0}} & \infty & \infty & 10 & \infty & 30 & 100 \\ {{v}_{1}} & \infty & \infty & 5 & \infty & \infty & \infty \\ {{v}_{2}} & \infty & \infty & \infty & 50 & \infty & \infty \\ {{v}_{3}} & \infty & \infty & \infty & \infty & \infty & 10 \\ {{v}_{4}} & \infty & \infty & \infty & 20 & \infty & 60 \\ {{v}_{5}} & \infty & \infty & \infty & \infty & \infty & \infty \\ \end{matrix} \right] graphv0v1v2v3v4v5v0∞∞∞∞∞∞v1∞∞∞∞∞∞v2105∞∞∞∞v3∞∞50∞20∞v430∞∞∞∞∞v5100∞∞1060∞
则迪杰斯特拉算法求解过程:

3.算法实现
迪杰斯特拉算法:
void ShortestPath_DIJ(MGraph G, int v0, int* path, int* distance) {
/*用Dijkstra算法求v0到其余顶点的最短路径p[n]和带权长度distance[n]
* final[i]=1表示已求得v0到vi的最短路径
*/
int* final = new int[G.vexnum];
int i;
// 初始化结点
for (i = 0; i < G.vexnum; i++) {
path[i] = -1;
final[i] = 0;
distance[i] = INT_MAX;
}
distance[v0] = 0; // 初始化源点
for (i = 0; i < G.vexnum; i++) { // 寻找不同长度的最短路径
int min = INT_MAX; // 当前所知里v0顶点的最短距离
int index = -1; // 最短距离对应的下标
int j;
for (j = 0; j < G.vexnum; j++) {
if (!final[j]) { // j在V-S中
if (distance[j] < min) {
min = distance[j];
index = j;
}
}
}// 寻找最短路径
if (index == -1) break;
final[index] = 1; // 离v0最近的顶点Index并入S,第一次一定是v0
for (j = 0; j < G.vexnum; j++) { // 更新距离矩阵,一定要判断是否达,即arcs[][]!=OO,否则会整数溢出
if (!final[j] && G.arcs[index][j] != INT_MAX && (min + G.arcs[index][j] < distance[j])) {
distance[j] = min + G.arcs[index][j];
path[j] = index;
}
}
}
delete[] final;
}
完整测试用例
#include <iostream>
#include <string>
#include <stack>
using namespace std;
#define MAX_VEX_NUM 50
// 定义图的邻接矩阵存储
typedef struct MGraph {
int arcs[MAX_VEX_NUM][MAX_VEX_NUM];
string vexs[MAX_VEX_NUM];
int arcnum, vexnum;
}MGraph;
void createGraph(MGraph& G);
int locate(MGraph G, string u);
void ShortestPath_DIJ(MGraph G,int v0,int* path,int* distance);
void showMatrix(MGraph G);
void showpath(MGraph G,int* path, string u); // 打印从v0到u的路径
int main() {
MGraph G;
createGraph(G);
showMatrix(G);
int* path = new int[G.vexnum];
int* distance = new int[G.vexnum];
ShortestPath_DIJ(G, 0,path,distance);
cout << "最后的路径数组:\n";
cout << "V0--V1:"; showpath(G, path, "V1");
cout << "V0--V2:"; showpath(G, path, "V2");
cout << "V0--V3:"; showpath(G, path, "V3");
cout << "V0--V4:"; showpath(G, path, "V4");
cout << "V0--V5:"; showpath(G, path, "V5");
system("pause");
return 0;
}
void showpath(MGraph G,int* path, string u) {
stack<int> stk;
int v = locate(G, u);
stk.push(v);
int parent = path[v];
if (parent == -1) cout << "V0到" << u << "没有路径";
while (parent != -1) {
stk.push(parent);
parent = path[parent];
}
while (!stk.empty()) {
int index = stk.top(); stk.pop();
cout << G.vexs[index];
if (!stk.empty()) cout << "-->";
}
cout << endl;
}
void showMatrix(MGraph G) {
cout << "图的邻接矩阵:\n";
for (int i = 0; i < G.vexnum; i++) {
for (int j = 0; j < G.vexnum; j++) {
if (G.arcs[i][j] == INT_MAX)
cout << "oo" << " ";
else
cout << G.arcs[i][j] << " ";
}
cout << endl;
}
}
void createGraph(MGraph& G) {
cout << "输入图的顶点数和边数:\n";
cin >> G.vexnum >> G.arcnum;
cout << "输入图的顶点信息:\n";
int i;
for (i = 0; i < G.vexnum; i++) cin >> G.vexs[i];
// 初始化邻接矩阵
int j;
for (i = 0; i < G.vexnum; i++) {
for (j = 0; j < G.vexnum; j++)
G.arcs[i][j] = INT_MAX;
}
cout << "输入图的边的权值信息vi vj weight:\n";
for (i = 0; i < G.arcnum; i++) {
string v1, v2;
int weight;
cin >> v1 >> v2 >> weight;
int l1 = locate(G, v1);
int l2 = locate(G, v2);
G.arcs[l1][l2] = weight;
}
}
int locate(MGraph G, string u) {
int i;
for (i = 0; i < G.vexnum && G.vexs[i] != u; i++);
if (i == G.vexnum) return -1;
else return i;
}
这里我用的是栈倒序输出,当然也有其它存储最短路径的方法。
测试用例(即上面的有向网):
6 8
V0 V1 V2 V3 V4 V5
V0 V5 100
V0 V4 30
V0 V2 10
V1 V2 5
V2 V3 50
V3 V5 10
V4 V5 60
V4 V3 20
4.时间复杂度
两个
F
O
R
FOR
FOR 循环嵌套,最后的时间复杂度为
O
(
n
2
)
O(n^{2})
O(n2) 。
若想要求源点到某一顶点的最短路径,也可以用
D
i
j
k
s
t
r
a
Dijkstra
Dijkstra 算法,复杂度一样;
但是若要求每一对顶点间的最短路径,可以调用Dijkstra算法n次,复杂度
O
(
n
3
)
O(n^{3})
O(n3) ,也可以用弗洛伊德算法(Floyd),复杂度也是
O
(
n
3
)
O(n^{3})
O(n3),但是形式上看起来简单一点。
最短路径之弗洛伊德算法(Floyd)——动态规划
Wayward:)
弗洛伊德算法(Floyd)主要针对多源最短路径,且可以解决路径中有负权的情况(不包含负权回路),但是迪杰斯特拉算法只能解决正权值的单源最短路径(可以迭代多次求多源)。
1.弗洛伊德算法的基本思想
弗洛伊德算法从图的带权邻接矩阵cost出发,
假设求从顶点
v
i
v_{i}
vi到
v
j
v_{j}
vj的最短路径:
如果从
v
i
v_{i}
vi 到
v
j
v_{j}
vj 有弧,则从
v
i
v_{i}
vi 到
v
j
v_{j}
vj 存在一条长度为
a
r
c
s
[
i
]
[
j
]
arcs[i][j]
arcs[i][j] 的路径,但是不一定是最短的,还需进行
n
n
n 次试探。
- 首先考虑 k = 0 k=0 k=0 时,路径 ( v i , v 0 , v j ) (v_{i},v_{0},v_{j}) (vi,v0,vj) 是否存在,若存在则判断 ( v i , v j ) (v_{i},v_{j}) (vi,vj) 和 ( v i , v 0 , v j ) (v_{i},v_{0},v_{j}) (vi,v0,vj) 的路径长度较短者,为从 v i v_{i} vi 到 v i v_{i} vi 的中间顶点的序号不大于0的最短路径;
- 假设在路径上再增加一个节点 v 1 v_{1} v1,也就是说如果 ( v i , . . . , v 1 ) (v_{i},...,v_{1}) (vi,...,v1) 和 ( v 1 , . . . , v j ) (v_{1},...,v_{j}) (v1,...,vj) 分别是当前找到的中间顶点的序号不大于0的最短路径,那么 ( v i , . . . , v 1 , . . . , v j ) (v_{i},...,v_{1},...,v_{j}) (vi,...,v1,...,vj) 就有可能是从 v i v_{i} vi 到 v j v_{j} vj 中间的顶点的序号不大于 1 的最短路径。将它和从 v i v_{i} vi 到 v j v_{j} vj 中间的顶点的序号不大于 0 的最短路径,从中选最小者为最后从 v i v_{i} vi 到 v j v_{j} vj 中间的顶点的序号不大于1的最短路径;
- 依次类推,逐渐增加中间顶点的序号值到 n − 1 n-1 n−1,则经过 n n n 次比较后可求得从 v i v_{i} vi 到 v j v_{j} vj 的最短路径。
2.弗洛伊德算法的基本表示
弗洛伊德算法属于动态规划算法的一种,而动态规划算法的关键是:
- 如果将原问题划分为子问题?
即如何确定子问题的状态; - 如何通过子问题的求解得到原问题的解?
即如何建立状态转移方程(我喜欢叫它动态规划的递归方程)。
通过1的分析,我们可以这样来定义一个子问题:
即定义一个n阶方阵序列:
D − 1 , D 0 , D 1 , . . . , D k , . . . , D n − 1 D^{-1},D^{0},D^{1},...,D^{k},...,D^{n-1} D−1,D0,D1,...,Dk,...,Dn−1
其中, D k D^{k} Dk 表示当前求得的最短路径权值矩阵。
而动态递归方程:

其中, D ( k ) [ i ] [ j ] D^{(k)}[i][j] D(k)[i][j]表示从 v [ i ] v_[i] v[i]到 v j v_{j} vj的中间顶点的序号不大于k的最短路径的长度。 D ( n − 1 ) [ i ] [ j ] D^{(n-1)}[i][j] D(n−1)[i][j]就是我们最后要求的从 v i v_{i} vi到 v j v_{j} vj的最短路径。
3.弗洛伊德算法的实现
有了动态递归方程和初始条件,我们就可以编写代码像解数列公式一样求出各对顶点间的最短路径。
只需注意问题的初始化和主变量的确定,这个问题的主变量是中间顶点的标号,应该将其放在最外层循环,就像数列递推公式的下标一样。
为了存储最短路径序列,我们还需要一个数组path,且path[v][w]存储
v
v
v到
w
w
w 的最短路径上
v
v
v 的后继节点,初始化为
w
w
w 。最后可以通过递归打印
v
v
v 到
w
w
w 的最短路径。
关键代码:
void ShortestPath_FLOYD(MGraph G, int** path, int**distance) {
// 初始化
int u, v, w;
for ( u = 0; u < G.vexnum; u++) {
for (v = 0; v < G.vexnum; v++) {
distance[u][v] = G.arcs[u][v];
path[u][v] = v;
}
}
// 3个FOR循环更新ditance共n-1次
for (u = 0; u < G.vexnum; u++) {
for (v = 0; v < G.vexnum; v++) {
for (w = 0; w < G.vexnum; w++) {
// 设置中间变量,防止整数溢出;
// 当然,若非邻接顶点的表示不是INT_MAX,而是一个非最大值的大数则不用
int temp = (distance[v][u] == INT_MAX || distance[u][w] == INT_MAX) ?
INT_MAX : (distance[v][u] + distance[u][w]);
if (temp < distance[v][w]) {
distance[v][w] = temp;
path[v][w] = path[v][u]; // 更新v的直接后继
}
}
}
}
}
完整代码:
#include <iostream>
#include <string>
using namespace std;
#define MAX_VEX_NUM 50
// 定义图的邻接矩阵存储
typedef struct MGraph {
int arcs[MAX_VEX_NUM][MAX_VEX_NUM];
string vexs[MAX_VEX_NUM];
int arcnum, vexnum;
}MGraph;
void createGraph(MGraph& G);
int locate(MGraph G, string u);
void showMatrix(MGraph G);
void ShortestPath_FLOYD(MGraph G, int** path, int**distance);
void showPath(MGraph G,int** path, int start, int end); //打印start-end的最短路径
int main() {
MGraph G;
createGraph(G);
showMatrix(G);
int** path; // 记录各个最短路径的信息
int** distance; // 记录权值矩阵
path = new int*[G.vexnum];
distance = new int*[G.vexnum];
for (int i = 0; i < G.vexnum; i++) {
path[i] = new int[G.vexnum];
distance[i] = new int[G.vexnum];
}
ShortestPath_FLOYD(G, path, distance);
cout << "path矩阵:\n";
for (int i = 0; i < G.vexnum; i++) {
for (int j = 0; j < G.vexnum; j++) cout << path[i][j];
cout << endl;
}
showPath(G, path,0, 2);
// 释放内存
for (int i = 0; i < G.vexnum; i++) {
delete[] path[i];
delete[] distance[i];
}
delete[] path;
delete[] distance;
system("pause");
return 0;
}
void showPath(MGraph G,int** path, int start, int end) {
int begin = path[start][end];
cout << G.vexs[start] << "到" << G.vexs[end] << "的最短路径:\n";
cout << G.vexs[start];
while (begin != end) {
cout << "-->" << G.vexs[begin];
begin = path[begin][end]; // 迭代后半段
}
cout << "-->" << G.vexs[end] << endl;
}
void showMatrix(MGraph G) {
cout << "图的邻接矩阵:\n";
for (int i = 0; i < G.vexnum; i++) {
for (int j = 0; j < G.vexnum; j++) {
if (G.arcs[i][j] == INT_MAX)
cout << "oo" << " ";
else
cout << G.arcs[i][j] << " ";
}
cout << endl;
}
}
void createGraph(MGraph& G) {
cout << "输入图的顶点数和边数:\n";
cin >> G.vexnum >> G.arcnum;
cout << "输入图的顶点信息:\n";
int i;
for (i = 0; i < G.vexnum; i++) cin >> G.vexs[i];
// 初始化邻接矩阵
int j;
for (i = 0; i < G.vexnum; i++) {
for (j = 0; j < G.vexnum; j++)
G.arcs[i][j] = INT_MAX;
}
cout << "输入图的边的权值信息vi vj weight:\n";
for (i = 0; i < G.arcnum; i++) {
string v1, v2;
int weight;
cin >> v1 >> v2 >> weight;
int l1 = locate(G, v1);
int l2 = locate(G, v2);
G.arcs[l1][l2] = weight;
}
}
测试用例:
3 5
V0 V1 V2
V0 V1 4
V0 V2 11
V1 V0 6
V1 V2 2
V2 V0 3
即带权有向图:

参考资料
《数据结构 C 语言版》 严蔚敏著
Dijkstra算法详细讲解
逆风行砾 于 2018-06-12 12:46:34 发布
迪杰斯特拉(Dijkstra) 算法是典型最短路径算法,用于计算一个节点到其他节点的最短路径。
它的主要特点是以起始点为中心向外层层扩展(广度优先搜索思想),直到扩展到终点为止。
基本思想
-
通过 Dijkstra 计算图 G 中的最短路径时,需要指定起点 s(即从顶点 s 开始计算)。
-
此外,引进两个集合 S 和 U。S 的作用是记录已求出最短路径的顶点(以及相应的最短路径长度),而 U 则是记录还未求出最短路径的顶点(以及该顶点到起点 s 的距离)。
-
初始时,S 中只有起点 s;U 中是除 s 之外的顶点,并且 U 中顶点的路径是” 起点 s 到该顶点的路径”。然后,从 U 中找出路径最短的顶点,并将其加入到 S 中;接着,更新 U 中的顶点和顶点对应的路径。 然后,再从 U 中找出路径最短的顶点,并将其加入到 S 中;接着,更新 U 中的顶点和顶点对应的路径。 … 重复该操作,直到遍历完所有顶点。
操作步骤
-
初始时,S 只包含起点 s;U 包含除 s 外的其他顶点,且 U 中顶点的距离为” 起点 s 到该顶点的距离”[例如,U 中顶点 v 的距离为(s,v) 的长度,然后 s 和 v 不相邻,则 v 的距离为∞]。
-
从 U 中选出” 距离最短的顶点 k”,并将顶点 k 加入到 S 中;同时,从 U 中移除顶点 k。
-
更新 U 中各个顶点到起点 s 的距离。之所以更新 U 中顶点的距离,是由于上一步中确定了 k 是求出最短路径的顶点,从而可以利用 k 来更新其它顶点的距离;例如,(s,v) 的距离可能大于(s,k)+(k,v) 的距离。
-
重复步骤(2) 和(3),直到遍历完所有顶点。
单纯的看上面的理论可能比较难以理解,下面通过实例来对该算法进行说明。
图解

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

初始状态
- 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)。
代码
邻接矩阵为例,
// 邻接矩阵
typedef struct _graph
{
char vexs[MAX]; // 顶点集合
int vexnum; // 顶点数
int edgnum; // 边数
int matrix[MAX][MAX]; // 邻接矩阵
}Graph, *PGraph;
// 边的结构体
typedef struct _EdgeData
{
char start; // 边的起点
char end; // 边的终点
int weight; // 边的权重
}EData;
Graph是邻接矩阵对应的结构体。
vexs用于保存顶点,vexnum是顶点数,edgnum 是边数;matrix 则是用于保存矩阵信息的二维数组。
例如,matrix[i][j]=1,则表示”顶点 i(即vexs[i])”和”顶点 j(即vexs[j])”是邻接点;matrix[i][j]=0,则表示它们不是邻接点。
EData 是邻接矩阵边对应的结构体。
Dijkstra算法
/*
* Dijkstra最短路径。
* 即,统计图(G)中"顶点vs"到其它各个顶点的最短路径。
*
* 参数说明:
* G -- 图
* vs -- 起始顶点(start vertex)。即计算"顶点vs"到其它顶点的最短路径。
* prev -- 前驱顶点数组。即,prev[i]的值是"顶点vs"到"顶点i"的最短路径所经历的全部顶点中,位于"顶点i"之前的那个顶点。
* dist -- 长度数组。即,dist[i]是"顶点vs"到"顶点i"的最短路径的长度。
*/
void dijkstra(Graph G, int vs, int prev[], int dist[])
{
int i,j,k;
int min;
int tmp;
int flag[MAX]; // flag[i]=1表示"顶点vs"到"顶点i"的最短路径已成功获取。
// 初始化
for(i = 0; i < G.vexnum; i++)
{
flag[i] = 0; // 顶点i的最短路径还没获取到。
prev[i] = 0; // 顶点i的前驱顶点为0。
dist[i] = G.matrix[vs][i];// 顶点i的最短路径为"顶点vs"到"顶点i"的权。
}
// 对"顶点vs"自身进行初始化
flag[vs] = 1;
dist[vs] = 0;
// 遍历G.vexnum-1次;每次找出一个顶点的最短路径。
for(i = 1; i < G.vexnum; i++)
{
// 寻找当前最小的路径;
// 即,在未获取最短路径的顶点中,找到离vs最近的顶点(k)。
min = INF;
for(j = 0; j < G.vexnum; j++)
{
if(flag[j]==0 && dist[j]<min)
{
min = dist[j];
k = j;
}
}
// 标记"顶点k"为已经获取到最短路径
flag[k] = 1;
// 修正当前最短路径和前驱顶点
// 即,当已经"顶点k的最短路径"之后,更新"未获取最短路径的顶点的最短路径和前驱顶点"。
for(j = 0; j < G.vexnum; j++)
{
tmp =(G.matrix[k][j]==INF ? INF :(min + G.matrix[k][j])); // 防止溢出
if(flag[j] == 0 &&(tmp < dist[j]) )
{
dist[j] = tmp;
prev[j] = k;
}
}
}
// 打印dijkstra最短路径的结果
printf("dijkstra(%c): \n", G.vexs[vs]);
for(i = 0; i < G.vexnum; i++)
printf(" shortest(%c, %c)=%d\n", G.vexs[vs], G.vexs[i], dist[i]);
}
参考资料
-
Dijkstra算法(一)之 C语言详解 - 如果天空不死 - 博客园
-
Dijkstra算法详细讲解_迪杰斯特拉算法-优快云博客 逆风行砾 于 2018-06-12 12:46:34 发布
https://blog.youkuaiyun.com/qq_37796444/article/details/80663810
Dijkstra算法图文详解
black-hole6 已于 2022-03-07 18:21:24 修改
Dijkstra 算法
Dijkstra 算法算是贪心思想实现的,首先把起点到所有点的距离存下来找个最短的,然后松弛一次再找出最短的,所谓的松弛操作就是,遍历一遍看通过刚刚找到的距离最短的点作为中转站会不会更近,如果更近了就更新距离,这样把所有的点找遍之后就存下了起点到其他所有点的最短距离。
问题引入:
指定一个点(源点)到其余各个顶点的最短路径,也叫做 “单源最短路径”。例如求下图中的 1 号顶点到 2、3、4、5、6 号顶点的最短路径。








下面我们来模拟一下:


这就是 Dijkstra 算法的基本思路:
接下来是代码:
已经把几个过程都封装成了基本模块:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#define Inf 0x3f3f3f3f
using namespace std;
int map[1005][1005];
int vis[1005],dis[1005];
int n,m;//n个点,m条边
void Init ()
{
memset(map,Inf,sizeof(map));
for(int i=1;i<=n;i++)
{
map[i][i]=0;
}
}
void Getmap()
{
int u,v,w;
for(int t=1;t<=m;t++)
{
scanf("%d%d%d",&u,&v,&w);
if(map[u][v]>w)
{
map[u][v]=w;
map[v][u]=w;
}
}
}
void Dijkstra(int u)
{
memset(vis,0,sizeof(vis));
for(int t=1;t<=n;t++)
{
dis[t]=map[u][t];
}
vis[u]=1;
for(int t=1;t<n;t++)
{
int minn=Inf,temp;
for(int i=1;i<=n;i++)
{
if(!vis[i]&&dis[i]<minn)
{
minn=dis[i];
temp=i;
}
}
vis[temp]=1;
for(int i=1;i<=n;i++)
{
if(map[temp][i]+dis[temp]<dis[i])
{
dis[i]=map[temp][i]+dis[temp];
}
}
}
}
int main()
{
scanf("%d%d",&m,&n);
Init();
Getmap();
Dijkstra(n);
printf("%d\n",dis[1]);
return 0;
}
Dijkstra算法详解(完美图解、趣学算法)
wjyGrit 已于 2022-03-18 19:52:58 修改
Dijkstra算法简介
Dijkstra 算法是解决单源最短路径问题的贪心算法
它先求出长度最短的一条路径,再参照该最短路径求出长度次短的一条路径,直到求出从源点到其他各个顶点的最短路径。
Dijkstra算法的基本思想
首先假定源点为u,顶点集合V被划分为两部分:集合 S 和 V-S。
初始时S中仅含有源点u,其中S中的顶点到源点的最短路径已经确定。
集合S 和V-S中所包含的顶点到源点的最短路径的长度待定,称从源点出发只经过S中的点到达V-S中的点的路径为特殊路径,
并用dist[]记录当前每个顶点对应的最短特殊路径长度。
Dijkstra贪心策略
选择特殊路径长度最短的路径,将其连接的 V-S 中的顶点加入到集合 S 中,同时更新数组 dist[]。一旦 S 包含了所有顶点,dist[] 就是从源到所有其他顶点的最短路径长度。
(1)数据结构。 设置地图的带权邻接矩阵为 map[][],即如果从源点 u 到顶点 i 有边,就令 map[u][i]=<u,i> 的权值,否则 map[u][i]=∞;
采用一维数组 dist[i] 来记录从源点到 i 顶点的最短路径长度:采用一维数组 p[i] 来记录最短路径上 i 顶点的前驱。
(2)初始化。令集合 S={u},对于集合 V-S 中的所有顶点 x,初始化 dist[i]=map[u][i], 如果源点 u 到顶点 i 有边相连,初始化 p[i]=u (i 的前驱是 u), 否则 p[i]=-1
(3)找最小。在集合 V-S 中依照贪心策略来寻找使得 dist[j] 具有最小值的顶点 t, 即 dist[t]=min,则顶点 t 就是集合 V-S 中距离源点 u 最近的顶点。
(4)加入 S 战队。将顶点 t 加入集合 S,同时更新 V-S
(5)判结束。如果集合 V-S 为空,算法结束,否则转 6
(6)借东风。在(3)中已近找到了源点到 t 的最短路径,那么对集合 V-S 中所有与顶点 t 相邻的顶点 j,都可以借助 t 走捷径。
如果 dist[j]>dist[t]+map[t][j], 则 dist[j]=dist[t]+map[t][j],记录顶点 j 的前驱为 t,p[j]=t,转(3)。
// 我自己在这里理解就是,从 u 找到与它最近的点 t,在从 t 找到与它最近的点 j,在… 按照这样持续下去,直到最后一个点这里我再通俗的解释下这个借东风的意思。
源点为 1,如果我们找到了距离源点最近的点 2,且点 2 与 3,4 相连。
这样,我们如果要倒 3,4 有两种方法:
1->2->3 (4)
1->3 (4)
这里我们就要判断是从 1 直接到 3 (4) 快,还是经过 2 后快。假设 <1,2>=2 / <2,3>=3 / <1,3>=4
根据上面的数据,我们第一次找最小找到的是 2 结点,如果我们直接把 2 替换掉 1 当做源点继续找下一个最近的点,这种方法是错的。
因为可以看出 1->3 只用 4,而过 2 的话要用 5。
完美图解
这里我就直接放图片了,书里的图不好画。但主要的是自己按照其流程过一遍,在草稿纸上自己画一遍,本书的网盘地址在文章末。





伪代码详解
跟着图解大致了解了一遍接下来就要上代码了,放心,代码不是一个完整的几十行的代码,全部按步骤划分好了的,这里方便大家粘贴。
/*
(1)数据结构
n:城市顶点个数. m:城市间路线的条数. map[][]:地图对应的带权邻接矩阵. dist[]:记录源点u到某顶点的最短路径长度。
p[]:记录源点到某顶点的最短路径上的该顶点的前一个顶点(前驱).flag[]:flag[i]=true说明顶点i已加入到集合S,否则该顶点属于集合V-S
*/
const int N=100;//初始化城市个数,可修改
const int INF=1e7; //无穷大
int map[N][N],dist[N],p[N],n,m;
bool flag[N];
//(2)初始化源点u到其他各个顶点的最短路径长度,初始化源点u出边邻接点的前驱为u
bool flag[n];//如果flag[i]=true,说明该顶点i已经加入到集合S;否则i属于集合V-S
for(int i=1;i<=n;i++){
dist[i]=map[u][i]; //初始化源点u到其他各个顶点的最短路径长度
flag[i]=false;
if(dist[i]==INF)
p[i]=-1; //说明源点u到顶点i无边相连,设置p[i]=-1
else
p[i]=u; //说明源点u到顶点i有边相连,设置p[i]=u
}
//(3)初始化集合S,令集合S={u},从源点u的最短路径为0
flag[u]=true;//初始化集合S中,只有一个元素:源点u
dist[u]=0; //初始化源点u的最短路径为0,自己到自己的最短路径
//(4)找最小.在集合V-S中寻找距离源点u最近的顶点t,若找不到,则跳出循环;否则,将t加入集合S。
int temp=INF,t=u;
for(int j=1;j<=n;j++){//在集合V-S中寻找距离源点u最近的顶点t
if(!flag[j] && dist[j]<temp){
t=j; //记录距离源点u最近的顶点
temp=dist[j];
}
}
if(t==u) return ; //找不到t跳出循环
flag[t]=true; //否则,将t加入集合S
//(5)借东风。考察集合V-S中源点u到t的邻接点j的距离,如果源点u经过t到达j的路径更短,
// 则更新dist[j]=dist[t]+map[t][j],即松弛操作,并记录j的前驱为t;
for(int j=1;j<=n;j++){//更新集合V-S中与t邻接的顶点到u的距离
if(!flag[j] && map[t][j]<INF){//!flag[j]表示j在v-s集合中,map[t][j]<INF表示t与j邻接
if(dist[j]>(dist[t]+map[t][j])){//经过t到达j的路径更短
dist[j]=dist[t]+map[t][j];
p[j]=t; //记录j的前驱为t
}
}
}
//重复(4)~(5),知道源点u到所有顶点的最短路径被找到
完整代码
#include<bits/stdc++.h>
using namespace std;
const int N=100; //城市个数可修改
const int INF=1e7; //初始化无穷大为.......
int map[N][N],dist[N],p[N],n,m; //n为城市个数,m为城市间路线的条数
bool flag[N]; //如果flag[i]=true,说明该顶点i已经加入到集合S;否则i属于集合V-S
void Dijkstra(int u){
for(int i=1;i<=n;i++){//********>>>--1--<<<******//
dist[i]=map[u][i]; //初始化源点u到其他各个顶点的最短路径长度
flag[i]=false;
if(dist[i]==INF)
p[i]=-1; //说明源点u到顶点i无边相连,设置p[i]=-1
else
p[i]=u; //说明源点u到顶点i有边相连,设置p[i]=u
}
flag[u]=true;//初始化集合S中,只有一个元素:源点u
dist[u]=0; //初始化源点u的最短路径为0,自己到自己的最短路径
for(int i=1;i<=n;i++){//********>>>--2--<<<******//
int temp=INF,t=u;
for(int j=1;j<=n;j++){//>>--3--<<在集合V-S中寻找距离源点u最近的顶点t
if(!flag[j] && dist[j]<temp){
t=j; //记录距离源点u最近的顶点
temp=dist[j];
}
}
if(t==u) return ; //找不到t跳出循环
flag[t]=true; //否则,将t加入集合S
for(int j=1;j<=n;j++){//>>--4--<<更新集合V-S中与t邻接的顶点到u的距离
if(!flag[j] && map[t][j]<INF){//!flag[j]表示j在v-s集合中,map[t][j]<INF表示t与j邻接
if(dist[j]>(dist[t]+map[t][j])){//经过t到达j的路径更短
dist[j]=dist[t]+map[t][j];
p[j]=t; //记录j的前驱为t
}
}
}
}
}
int main(){
int u, v, w, st;
system("color 0d");
cout <<"请输入城市的个数:" <<endl;
cin>> n;
cout <<"请输入城市之间的路线个数" <<endl;
cin>> m;
cout <<"请输入城市之间的路线以及距离" <<endl;
for(int i=1;i<=n;i++)//初始化图的邻接矩阵
for (int j = 1; j <= n; j++)
{
map[i][j] = INF;//初始化邻接矩阵为无穷大
}
while (m--)
{
cin>> u>> v>> w;
map[u][v] = min(map[u][v], w); //邻接矩阵存储,保留最小的距离
}
cout <<"请输入小明所在的位置:" <<endl;
cin>> st;
Dijkstra(st);
cout <<"小明所在的位置:" <<st <<endl;
for (int i = 1; i <= n; i++)
{
cout <<"小明:" <<st <<" - " <<"要去的位置:" <<i <<endl;
if (dist[i] == INF)
cout <<"sorry,无路可达" <<endl;
else
cout <<"最短距离为:" <<dist[i] <<endl;
}
return 0;
}
输入
请输入城市的个数:
5
请输入城市之间的路线个数
11
请输入城市之间的路线以及距离
1 5 2
5 1 8
1 2 16
2 1 29
5 2 32
2 4 13
4 2 27
1 3 15
3 1 21
3 4 7
4 3 19
请输入小明所在的位置:
5
输出
小明所在的位置:5
小明:5 - 要去的位置:1 最短距离为:8
小明:5 - 要去的位置:2 最短距离为:24
小明:5 - 要去的位置:3 最短距离为:23
小明:5 - 要去的位置:4 最短距离为:30
小明:5 - 要去的位置:5 最短距离为:0
因为我们在程序中使用了p[]数组记录了最短路径上每一个结点的前驱,所以我们可以增加一段程序逆向该最短路径上的城市序列。
void findpath(int u)
{
int x;
stack<int>s;
cout <<"源点为:" <<u <<endl;
for (int i = 1; i <= n; i++)
{
x = p[i];
while (x != -1)
{
s.push(x);
x = p[x];
}
cout <<"源点到其他各顶点的最短路径为:";
while (!s.empty())
{
cout <<s.top() <<"--";
s.pop();
}
cout <<i <<";最短距离为:" <<dist[i] <<endl;
}
}
只需要在主函数末尾调用即可
结果为:
源点为:5
源点到其他各顶点的最短路径为:5--1;最短距离为:8
源点到其他各顶点的最短路径为:5--1--2;最短距离为:24
源点到其他各顶点的最短路径为:5--1--3;最短距离为:23
源点到其他各顶点的最短路径为:5--1--3--4;最短距离为:30
源点到其他各顶点的最短路径为:5;最短距离为:0
算法解析及优化拓展



使用优先队列的完整代码
#include<queue>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100;//城市的个数可修改
const int INF = 1e7;//初始化无穷大为10000000
int map[N][N], dist[N], p[N], n, m;//n为城市的个数,m为城市间路线的条数
int flag[N]; // 如果flag[i]==true,说明顶点i已经加入到集合S;否则顶点i属于集合V-S
struct Node {
int u, step;
Node() {};
Node(int a, int sp)
{
u = a, step = sp;
}
bool operator<(const Node& a)const {//重载 <
return step> a.step;
}
};
void Dijkstra(int st)
{
priority_queue<Node>Q;//优先队列优化
Q.push(Node(st, 0));
memset(flag, 0, sizeof(flag));//初始化flag数组为0
for (int i = 1; i <= n; ++i)
dist[i] = INF;//初始化所有距离为无穷大
dist[st] = 0;
while (!Q.empty())
{
Node it = Q.top();//优先队列列头元素为最小值
Q.pop();
int t = it.u;
if (flag[t])//说明已经找到了最短距离,该节点是队列里面的重复元素
continue;
flag[t] = 1;
for (int i = 1; i <= n; i++)
{
if(!flag[i] && map[t][i]<INF)//判断与当前点有关系的点,并且自己不能到自己
if (dist[i]> dist[t] + map[t][i])
{
//求距离当前点的每个点的最短距离,进行松弛操作
dist[i] = dist[t] + map[t][i];
Q.push(Node(i, dist[i]));//把更新后的最短距离压入队列中,注意:里面有重复元素
}
}
}
}
int main()
{
int u, v, w, st;
system("color 0d");
cout <<"请输入城市的个数:" <<endl;
cin>> n;
cout <<"请输入城市之间的路线个数" <<endl;
cin>> m;
cout <<"请输入城市之间的路线以及距离" <<endl;
for (int i = 1; i <= n; i++)//初始化图的邻接矩阵
for (int j = 1; j <= n; j++)
{
map[i][j] = INF;//初始化邻接矩阵为无穷大
}
while (m--)
{
cin>> u>> v>> w;
map[u][v] = min(map[u][v], w); //邻接矩阵存储,保留最小的距离
}
cout <<"请输入小明所在的位置:" <<endl;
cin>> st;
Dijkstra(st);
cout <<"小明所在的位置:" <<st <<endl;
for (int i = 1; i <= n; i++)
{
cout <<"小明:" <<st <<" ---> " <<"要去的位置:" <<i;
if (dist[i] == INF)
cout <<"sorry,无路可达" <<endl;
else
cout <<" 最短距离为:" <<dist[i] <<endl;
}
return 0;
}
/*
请输入城市的个数:
5
请输入城市之间的路线个数
11
请输入城市之间的路线以及距离
1 5 2
5 1 8
1 2 16
2 1 29
5 2 32
2 4 13
4 2 27
1 3 15
3 1 21
3 4 7
4 3 19
请输入小明所在的位置:
5
小明所在的位置:5
小明:5 ---> 要去的位置:1 最短距离为:8
小明:5 ---> 要去的位置:2 最短距离为:24
小明:5 ---> 要去的位置:3 最短距离为:23
小明:5 ---> 要去的位置:4 最短距离为:30
小明:5 ---> 要去的位置:5 最短距离为:0
*/

相关题的题解
- Djikstra算法训练——题一:最小花费_酷町堂 -优快云博客 最小花费 2020/7/8
https://blog.youkuaiyun.com/qq_45776662/article/details/107216088
注:下文虽然作者声明【本文可能有一些不太严肃的胡说,请勿转载】但组织材料还是不错的。
Edsger W. Dijkstra – 巨人的肩膀
罗勇军 已于 2023-09-17 22:46:47 修改
本文为原创,可能有一些不太严肃的胡说,请勿转载。
不过,虽然本文有作者为活跃气氛而故作幽默的 “脑补”,但所有的事迹都严肃而确定,来源于 Dijkstra 本人的传记或权威的档案,见本文最后的 参考文献。
Dijkstra:宗师、巨星,计算机学科的奠基人之一
牛顿有句名言:“如果我看得比别人更远些,那是因为我站在巨人们的肩膀上1。
在计算机科学界,荷兰人 Edsger W. Dijkstra(EWD,狄克斯特拉)是公认的的巨人,抗起计算机学科发展的奠基人之一。从 22 岁到 70 岁近 40 年,他在计算机软件和理论方面做了很多开创性工作,伴随着计算机学科从懵懂到成熟的年代。他创造了大量常用的、写入权威教科书的基本计算机概念和术语,制订了很多编程技术的指导性原则,例如堆栈(stack)、显示(display)、信号量(semaphore)、死锁(deadlock)、互斥(mutual exclusion)、结构化编程(structured programming)等等。
Dijkstra 获得无数荣誉,其中最有名的是 ACM 图灵奖。1972 年第七届图灵奖,当 Dijkstra 得知自己获奖时,十分惊讶,因为这个美国 ACM 协会设置的奖很少颁给非美国人。而且,直到 24 年后的 1996 年,才有第 2 个非英美人获奖。
在 1966-2000 年间,共有 41 人获 ACM 图灵奖,其中:“美国出生 + 美国求学 + 美国工作”27 人;“非美国出生 + 美国求学 + 美国工作”8 人;其他 6 人,这 6 人中有 4 个英国人,1 个荷兰人(1972 年 Dijkstra),1 个以色列人(1996 年的 Amir Pnueli)。
2001-2019 年间,非美国获奖者多了起来。共 30 名获奖者,其中 “美国出生 + 美国求学 + 美国工作”18 人,“非美国出生 + 美国求学 + 美国工作”3 人,其他 9 人。
Dijkstra 简历:
1952–1962:荷兰阿姆斯特丹计算机部,程序员
1962–1973:荷兰埃因霍芬理工大学数学系,教授
1973–1984:美国 Burroughs Corporation 公司,研究员
1984–2000:美国德克萨斯大学奥斯汀分校,教授
1、引子
大部分计算机学生是在学习 “Dijkstra 最短路径算法” 时得知 Dijkstra 的。
图论中有两个著名的算法:Dijkstra 最短路径算法和 Prim 最小生成树算法 2。两个算法的思想基本一样,都是基于贪心法来扩展结点,执行步骤十分相似,代码只有微小差别。两个算法简单而高效,现在仍广泛应用在图问题中,例如手机的地图导航、游戏软件的寻路等。
这两个算法虽然以发明者的名字 “Dijkstra”、“Prim” 命名,但其实有好几位科学家独立发明过,其中一人是 Dijkstra。1956 年,他 26 岁做兼职程序员时曾在一篇论文中同时提出了这两个算法。这篇只有 3 页的小论文 “A note on two problems in connection with graphs 3,第一部分现在一般被称为 “Prim 最小生成树算法”,第二部分现在一般被称为 “Dijkstra 最短路径算法”。
Dijkstra 最短路径算法的思路很简单,它维护两个集合:一个是已经计算出最短路径的结点集合 A,另一个是这些点向外扩散的邻居结点集合 B。算法的执行步骤是:扩展 A 的直连邻居,放到 B 中;每次从 B 中取出距离起点最短的结点,放到 A 中;当 B 为空时计算结束。

图 1 Dijkstra 原始论文中的集合 A 和集合 B
不过,论文中没有提到一个关键问题:如何高效地从 B 中选出距离起点最短的那个点?这个问题的解决决定了算法的复杂度,决定了代码的效率有多好。现在学过图论的学生都知道,编程时一般用优先队列(效率极高,n 个数只需要计算 logn 次就能找到最小的数)来实现。但是在 Dijkstra 的论文中,并没有提到如何实现,也许他认为这是一个需要扩展的问题,更大的可能是那时候还没有这些数据结构的概念。
Dijkstra 在 1956 年时发明了这个算法,但那时还没有自动计算方面的专业期刊,无法发表,直到 1959 年才找到合适的刊物发表。这篇论文是 1945-2000 年间 Web of Science 数据库中的一篇经典引文。在一次关于 Dijkstra 算法的通信中,Douglas McIlroy 教授谈到了 Dijkstra 算法表达的现代性,指出 “它在当时显然是不寻常的。后来的上千次引用也不能对它有所改进。” 也就是说,Dijkstra 在计算机科学理论初创时期所写的论文,已经十分规范而标准,并成了后来者的模板。
Dijkstra 对计算机学科的发展有很多奠基性的贡献,最短路径算法只是他年轻时走过的一个小脚印。Dijkstra 于 1956 年提出最短路径算法,时年 26 岁,是他作为一个计算机科学家的开端。之后的 30 年,他发展了现在众所周知的、成为常识的多种计算机概念和技术,他那 “巨人的肩膀,扛起了计算机科学 4(The Man Who Carried Computer Science on His Shoulders)”。
2、生平
简单地概况 Dijkstra 的成功而完美的人生:书香门第、天资聪颖、勤学好思、抓住机遇、工作努力、广泛交游。
1930 年,Dijkstra 出生于荷兰城市鹿特丹(Rotterdam),是四个孩子中的老三。他有个好家庭。他父亲是中学化学老师和中学校长,但不是一个普通的化学老师,而是一个化学家、荷兰化学学界的名人,当过荷兰化学协会的主席,博学多才。他母亲是数学家,不过没工作而是家庭妇女(那个年代的女性都这样),她非常聪明,是个天才,Dijkstra 说他从母亲那里学到了如何简洁优雅地解决问题。
Dijkstra 又聪明又勤学,学习成绩非常好。中学毕业后,他想去学法律,将来到联合国为世界和平做贡献,避免再次世界大战!但是身为科学家的爸妈都建议他,不要浪费自己的聪明才智,应该去从事科学事业。
1948 年,他到离家不远的莱顿大学(University of Leiden)读物理学,他说他的大学生涯 “很穷很累很快乐 5”。这不奇怪,那时正是二战后的欧洲重建时期。
大学的头三年,Dijkstra 还是那么勤奋,他老老实实学物理和数学,1951 年通过中期考试,比其他同学都早。然而 Dijkstra 一直到 1956 年才拿到大学学位,一共读了 8 年大学。读了这么久,不是因为去游戏人生了,而是因为他对物理的兴趣衰退了,中间去兼职工作当了一名程序员。
1951 年,一件事改变了 Dijkstra 的命运。计算机科学的一颗星星开始升起,这将是一颗星等为一等的亮星。
这一年,他那热爱科学并积极关注科学发展的老爸,看到剑桥大学的一个编程课广告,一门三星期的短课,学习 EDSAC 计算机编程。EDSAC 是世界第一台存储程序式电子计算机,采用了冯诺依曼结构。冯诺依曼结构后来成为现代计算机的标准架构,可见 EDSAC 是当时最先进的计算机。作为对 Dijkstra 提前通过中期考试的奖励,父亲资助他去英国学编程。估计真实目的是让他去剑桥大学见见世面,将来去剑桥读读研究生,顺便路过伦敦逛逛看看花花世界。
好学的 Dijkstra 第一时间想到的是:用计算机来帮助做数值计算,有利于物理研究!Dijkstra 高兴地去学编程了,从此与计算机结缘,走上了康庄大道。
Dijkstra 在这门编程短课上似乎发现了新大陆,学得如鱼得水。而且,他偶遇了荷兰阿姆斯特丹数学中心的负责人,获得了一个 “黄金就业机会”。负责人对 21 岁的大学三年级青年说:计算机刚起步,有光明的发展前景,注定成为一门显学,“小伙子你会成为计算机宗师的”。然后热情邀请他到数学中心做编程工作,而且宜早不宜迟,机会不等人,“快来快来,过几天就没位置了”。
Dijkstra 面临一个重大选择,是继续读物理,还是去搞编程?他决定一起做。而且,大学生活穷得很,兼职能赚点钱,有钱才能找女朋友!
从 1952 年开始,Dijkstra 大部分时间都在数学中心兼职搞编程,物理学业差不多荒废了,导致他到 1956 年才从大学毕业。毕业后,他开始在数学中心全职工作,一直到 1962 年。长达十年的编程工作,他自学成才,成长为一位计算机科学家。
他在自传中戏称自己是 “第一个靠编程赚钱的荷兰人 6”,在图灵奖获奖感言中也说自己是荷兰的第一个程序员。
看到这里,我们发现 Dijkstra 当初其实是被数学中心负责人忽悠了。荷兰那时几乎没有人会编程,连一个只学了 3 周编程的 Dijkstra 也成了稀缺人才,甚至是 “荷兰第一个程序员”。数学中心的负责人之所以向 Dijkstra 伸出橄榄枝,根本原因是没有选择,只能找他。
在数学中心当程序员,听起来不错:敲着键盘,喝着咖啡,工作之余还能打打游戏… 这都是做梦,那时还没有电脑键盘,输入靠穿孔纸带,输出靠电传打字机,电脑屏幕自然也没有。至于编程嘛,高级语言还没有出现,汇编语言也没有,因为电脑还没有编译编程语言的能力,只能直接用机器码编程,就是 0 和 1,也就是在纸带上穿孔,打孔就是 1,没打孔就是 0。没学过计算机的人可能不能理解为什么 “0101110011…” 这种东西就是程序。现在编程语言教科书上的知识,当时几乎都没有。
Dijkstra 在剑桥学的就是这种编程,怪不得只要 3 周,因为也没什么好学的。据我猜想,这 3 周的教学大纲估计长这样:第一课 “二进制”;第二课 “逻辑代数”;第三课 “逻辑电路”;第四课 “加法器电路”;第五课 “冯诺依曼结构”;第六课 “机器指令”;第七课 “穿孔技巧”;第八课 “故障和维修”;完。
Dijkstra 没有被这种枯燥的学习吓倒,反而产生了浓厚的兴趣,数学中心负责人肯定高兴坏了。
Dijkstra 在数学中心的十年编程工作颇有成效,最短路径算法就是这个期间发明的,这是 Dijkstra 设计的第一个著名算法,并以他的名字命名。Dijkstra 为此感到很得意,他在 1993 年的自传中回忆这一美妙时光:“最短路径算法以我的名字命名,让我出了名。当初我设计这个算法的时候,并没有拿着纸和笔冥思苦想。那是在阿姆斯特丹的一个露天咖啡馆,我和妻子(当时在谈恋爱,一年后的 1957 年结婚)晒着阳光喝着咖啡,然后我灵机一动想到了这个算法。” 谁还没有一个春风得意的年轻时代呢。
图 2 Dijkstra 在自传中提到最短路径算法
1960 年,Dijkstra 和同事一起编写 ALGOL 60 编译器,这是世界上第一个 ALGOL 60 编译器。开发过程中解决了很多关键问题,例如 1960 年 Dijkstra 在一篇论文 “Recursive Programming” 中,提出了术语 “堆栈(stack)”。多年以后,他得知这篇小论文让他在美国计算机学界出了名。
图 3 Dijkstra 在自传中提到 stack
因为在数学中心的工作,Dijkstra 于 1959 年获得了阿姆斯特丹大学的博士学位,博士论文题目是 “Communication with an Automatic Computer”,研究实时中断问题。
Dijkstra 在数学中心工作到 1962 年,这期间的工作让他成为一位有名的计算机科学家,他这颗星星开始在天空眨眼。
1962 年,他被聘为荷兰埃因霍芬理工大学(Eindhoven University of Technology)数学系的教授。不过,他谦虚地说自己其实是第 3 个候选人,只是因为前 2 人拒绝了聘请,才轮到他。学校给他安排的本职教学工作是上《数值分析》课,课余他的精力完全在计算机上,他组织了一个计算机研究小组,开发操作系统 “THE Multiprogramming System”。“THE” 系统创造了历史,是世界第一个松耦合、显式同步、协操作串行处理的操作系统,在大学的计算中心运行了 10 年。信号量(semaphore)、死锁(deadlock)等新概念就是在这个系统上提出的。他很为这个系统自豪,他到英国、美国交流这个系统的设计,获得了同行的高度认可,使他在计算机学界声名鹊起。
Dijkstra 最广为人知的事情,是他对 GOTO 语句的批评。当时人们在编程时,经常使用 GOTO 语句,随意跳转到程序的任意位置。“GOTO 语句,天马行空!” 这个语句极为暴力,让程序的流程变得支离破碎。敏锐的 Dijkstra 第一个认识到这个语句对程序逻辑、计算资源的的破坏性。1968 年他给 Communications of the ACM 写了一篇 2 页纸的通讯,信件原标题是 “反对 GO TO 语句(A case against the GO TO statement 7”,因为标题过于露骨,被编辑改名为较为温和的 “Go To Statement Considered Harmful” 后发表,引起了广泛的争论。当然,现在大家都知道他赢了,几乎没有人在编程的时候使用 GO TO 语句。这封信影响如此之大,以至于 “X considered harmful” 成了一个标题模板,经常被用于咆哮和指责的文章中。
1969 年,杰出的计算机科学家 Dijkstra 很郁闷:他不受自己工作的大学的待见,他的研究小组被解散。他为了平复自己郁闷的心情,躲起来花大气力写了一篇 80 多页的报告 “Notes on Structured Programming 8”,即 “结构化编程”。结构化编程现在是计算机程序默认的基本原则,不过那时还是一个全新的概念。
结构化编程是现代所有编程语言教材的基本内容,即程序有三种基本结构:顺序结构、选择结构和循环结构。现在的程序员会觉得,这是天经地义,不容置疑的法理。

图 1. “Notes on Structured Programming” 文中图示的顺序结构

图 2. “Notes on Structured Programming” 文中图示的选择结构

图 3. “Notes on Structured Programming” 文中图示的循环结构
Dijkstra 还没意识到他写了一篇辉煌的巨著。他觉得这只是一篇普通的报告,让朋友们看看就好了,于是复印 20 份,寄给了外国的计算机科学家朋友们。然而出乎他的意料,这篇报告获得了巨大的成功,“像野火一样传播(spread like wildfire)”,辗转复印了很多轮,Dijkstra 自己就认识一个住在偏远角落的人珍藏着这篇报告的第 5、6 代复印稿。可以计算一下,如果每人复印 20 份然后继续往下传,第 5 代就有 20^5=300 万!
“洛阳纸贵”,一时间搞计算机的人人都在看这篇伟大的报告,深深为 Dijkstra 充满哲人的洞见而折服。Dijkstra 后来分析为什么受到欢迎:“这是第一篇严肃讨论编程挑战的文章。”
这份报告终于奠定了 Dijkstra 计算机巨星、宗师的地位。
这是一篇定义程序设计原理的奠基性文献,图灵奖网站 这样评价这份报告:““Notes” advocates certain design principles which are now generally accepted in the computer science community: Large systems should be constructed out of smaller components; each component should be defined only by its interface and not by its implementation; smaller components may be designed following a similar process of decomposition, thus leading to a top-down style of design; the design should start by enumerating the “separable concerns” and by considering each group of concerns independently of the others; and mathematical logic is and must be the basis for software design.”
Dijkstra 声誉日隆。1972 年的一个下午,他正坐在学院的办公室发呆,突然接到一个美国电话,被告知获得了第七届 ACM 图灵奖。Dijkstra 非常震惊,反复确认才肯相信。他完全没想到自己能得这个奖,因为前 6 个图灵奖获得者都是讲英语的、工作于著名机构的美国人或英国人。他幸福地眩晕了 (I got even a bit overwhelmed)。
Dijkstra 的震惊是可以理解的,如果他晚一些年获得图灵奖,他会更加震惊,因为一直到 24 年之后的 1996 年,才有第 2 个非英美的获奖者。姚期智 2000 年获得图灵奖,是图灵奖唯一的华裔学者,当时他是美国国籍。姚期智于 2016 年放弃美籍,成为中华人民共和国公民。
然而,Dijkstra 在学校的地位却越来越尴尬。就在获得 ACM 图灵奖之后不久,他被迫从大学离职了,这事儿真让人啼笑皆非。大环境是计算机学科在 1960 年代中期才在英美的一些大学出现,而 Dijkstra 所在的大学那时还没有。他所在的数学学院的领导并不理解和支持他的计算机工作,觉得他不务正业。他搞的计算机研究,和学院其他老师做的数学研究似乎毫不搭边。另外,Dijkstra 在自传中用忧郁的语气回忆自己当时 “穿拖鞋、留胡子、态度傲慢”,这不修边幅的样子一点儿都不像个教授,倒是个 “完美的” 现代程序员的邋遢风格。Dijkstra 的人缘似乎不太好,他成了他所在大学的一个异类,学校甚至解散了他的研究小组,他成了一个孤家寡人。
甚至 1972 年获得图灵奖,这个计算机学界的崇高荣誉也没有让他在学校的地位有所好转。Dijkstra 在办公室接到获奖电话,从幸福的眩晕中恢复过来后,第一时间就跑去告诉院长,“我得图灵奖啦!” 以为自己能借此翻身,从此在学院里面横着走。然而院长大人似乎不知道什么是图灵奖,还脱口而出说了一句伤人的话:“你们搞计算机的,得奖不要太容易哦!(In the world of computing, they are rather lavish with prizes, aren’t they?)”
年轻的科学巨星、计算机宗师被说得如此不堪!Dijkstra 七窍生烟,刻苦铭心地记了一辈子,还把这句话写到了自传中。
不久后,Dijkstra 从大学离职了。
1973 年 8 月 1 日,43 岁的、年轻的计算机大师 Dijkstra 找了个新工作,新东家是当时著名的美国计算机制造商 Burroughs Corporation 公司,办公地点就在他荷兰的家里。这是一份美妙的工作,他没有具体任务,而是 “自由研究”,只需要每年去美国总部汇报几次就好了。在做 “自由研究员” 的那些年,他跑遍了世界各国,参加各种学术会议,在计算机的世界中自由飞翔。Dijkstra 得到了他的天堂。
然而,有始必有终,美妙时光不会永远持续。1984 年,他从 Burroughs Corporation 公司离职,因为 “Burroughs 的学术视野正在缩小,而我的视野正在扩大(Burroughs’s intellectual horizon was shrinking, my own was widening)。” Burroughs Corporation 公司在走下坡路,日子不好过开始节流了。
美国的好客和学术环境吸引着 Dijkstra。1984 年,他终于在 54 岁的年龄离开了荷兰,到美国德克萨斯大学奥斯汀分校做计算机教授。
15 年后的 1999 年,Dijkstra 于 69 岁退休。2002 年查出癌症,回到荷兰后不久去世。
Dijkstra 一生著述极多。他做事非常有条理,用自己名字的缩写 “EWD” 对这些文件进行命名和编号,一共有 1318 篇,德克萨斯大学把这 1318 篇作品整理成档案在网上公开。有趣的是,他是一位计算机科学家,却几乎不用电脑打字,而习惯于用钢笔书写。这 1318 篇作品,除了早期的一部分已经找不到手稿是打印的,后期的手稿都在,字迹工工整整,几乎没有涂改的痕迹。这些手稿的笔迹,数十年完全一致,显然出自同一人的手笔,不是别人誊抄的。有的手稿长达几十页,是很不容易的。
他的最后一篇 “EWD1318”,写于 2002 年 4 月,当时已经回到荷兰。他 4 个月后去世。
绚烂的生命之花,在日暮时分凋落。

图 4 最后的手稿:EWD1318
本文参考了以下原始资料:
[1] Dijkstra 自传:“From my Life”(Written because people ask me for these data.)https://www.cs.utexas.edu/users/EWD/ewd11xx/EWD1166.PDF
[2] Dijkstra 档案:E.W.Dijkstra Archive: Home page
https://www.cs.utexas.edu/users/EWD/
[3] ACM 图灵奖介绍:Edsger W. Dijkstra - A.M. Turing Award Laureate
https://amturing.acm.org/award_winners/dijkstra_1053701.cfm
via:
-
Estefania Cassingena Navone September 28, 2020
Dijkstra’s Shortest Path Algorithm - A Detailed and Visual Introduction _ -
最短路径之迪杰斯特拉算法(Dijkstra)——贪心算法_dijkstra算法贪心算法-优快云博客 Wayward:) 于 2019-03-23 14:34:34 发布
https://blog.youkuaiyun.com/Africa_South/article/details/88759768 -
最短路径之弗洛伊德算法(Floyd)——动态规划_floyed算法动态规划原理-优快云博客 Wayward:)于 2019-03-24 21:26:43 发布
https://blog.youkuaiyun.com/Africa_South/article/details/88781453 -
Dijkstra算法图文详解-优快云博客 black-hole6 已于 2022-03-07 18:21:24 修改
https://blog.youkuaiyun.com/lbperfect123/article/details/84281300 -
Edsger W. Dijkstra – 巨人的肩膀_e.w.dijkstra-优快云 博客 罗勇军 已于 2023-09-17 22:46:47 修改
https://blog.youkuaiyun.com/weixin_43914593/article/details/114755499 -
Dijkstra算法详解(完美图解、趣学算法)_dijkstra算法过程图解-优快云博客
https://blog.youkuaiyun.com/qq_45776662/article/details/107177424
Standing On The Shoulders Of Giants - Meaning & Origin Of The Phrase https://www.phrases.org.uk/meanings/268025.html ↩︎
R. C. Prim, “Shortest Connection Networks and some Generalizations,” Bell System Technical Journal, Vol. 36, 1957, pp. 1389-1401. ↩︎
Edsger W. Dijkstra, “A note on two problems in connection with graphs,” Numerische Mathematik 1, 1959, pp. 269–271. http://www-m3.ma.tum.de/foswiki/pub/MN0506/WebHome/dijkstra.pdf ↩︎
The Man Who Carried Computer Science on His Shoulders https://inference-review.com/article/the-man-who-carried-computer-science-on-his-shoulders ↩︎
Dijkstra 自传:“We were very poor, worked very hard, never slept enough and often did not eat enough, but life was incredibly exciting.” ↩︎
Dijkstra 自传:“the first Dutchman with the qualification ‘programmer’ on the payroll.” ↩︎
A case against the GO TO statement https://www.cs.utexas.edu/users/EWD/ewd02xx/EWD215.PDF ↩︎
“Notes on Structured Programming”: https://www.cs.utexas.edu/users/EWD/ewd02xx/EWD249.PDF ↩︎


735

被折叠的 条评论
为什么被折叠?



