最短路径问题:重新发明 Dijkstra 算法
By Long Luo
大学时学通信专业课程 计算机网络1 时,需要学习 TCP/IP 协议族 2 。在学习网络层的路由协议时, \(\textit{Dijkstra}\) 3 算法是必须要掌握的。不过读书时,对这个算法是懵懵懂懂的,当时是死记硬背记住的,并没有理解其原理,过了一段时间之后就完全忘记了。如今在 2022 年我重新学习 搜索算法4 时,已经彻底理解 \(\textit{Dijkstra}\) 算法了。更确切的说,如果我们掌握了其原理,我们也可以重新发明 \(\textit{Dijkstra}\) 算法,就让我们从一个自驾游例子开始吧!
到达目的地哪条路径最快?
假期时,我们想从深圳自驾去上海,怎么去呢?打开地图软件,地图会给你规划好几条路线,在找到这些路线的背后就用到了 \(\textit{A Star}\)5 算法 或者 \(\textit{Dijkstra}\) 算法,它们都属于 最短路径算法6 。这个例子解释 用来\(\textit{Dijkstra}\) 算法并不是特别好,但方便大家理解。
假如回到那个 2G 时代,没有导航软件只有一张地图,大家会怎么做呢?
我们可能要根据到高速和城市之间里程来计算,比如北上赣州,经南昌、杭州到上海和经福建、温州、宁波再到上海哪个更快,不同城市之间高速路况情况,每段之间耗时多长,最后得到了一条路线。虽然大部分人并不知道 \(\textit{Dijkstra}\) 算法,但并不妨碍我们天生就会用 \(\textit{Dijkstra}\) 算法,所以我们把这个思考过程写下来,实际上就是 \(\textit{Dijkstra}\) 算法。
让我们重新一步一步来发现 \(\textit{Dijkstra}\) 算法吧!
从 BFS 开始
由于找合适的国内城市路线图和制图太花时间,刚好维基上的 Breadth-first search algorithm7 的页面上就提供了德国国内城市地图的例子,这里就借用下,如下图所示:
如果我们从 法兰克福(Frankfurt) 出发,很容易得到到达各个城市的最快方式,如下图所示:
以下面这个动图为例,我们来看看 \(\textit{BFS}\) 是如何做的?
从上图可以看出,\(\textit{BFS}\) 的运行过程可以描述如下:
- 首先选择一个顶点作为起始结点,并将其染成蓝色,其余未访问结点为黑色;
- 将起始结点放入队列中;
- 从队列首部选出一个顶点,并找出所有与之邻接的结点,将找到的邻接结点放入队列尾部,将已访问过结点涂成蓝色,没访问过的结点是黑色。如果顶点的颜色是蓝色,表示已经发现并且放入了队列,如果顶点的颜色是黑色,表示还未访问;
- 按照同样的方法处理队列中的下一个结点,直到所有节点都访问完毕。
伪代码如下所示:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17procedure BFS(G, source) is
let Q be a queue
label source as explored
Q.enqueue(source)
while Q is not empty do
v := Q.dequeue()
if v is the goal then
return v
for all edges from v to w in G.adjacentEdges(v) do
if w is not labeled as explored then
label w as explored
w.parent := v
Q.enqueue(w)