什么是Dijkstra算法?
Dijkstra算法是用来寻找最短路径最著名的算法之一。具体来说,Dijkstra算法主要用来寻找一个边的权值不为负的有向图中的任意一点到其他任意结点(在两点相互联通的情况下)之间的最小路径。如果利用Dijkstra算法找出从一点出发,到图中其他所有点的最短路径,事实上我们就构造出了一个最短路径树(shortest-path tree)。
Dijkstra最短路径算法因其简单高效的特征而被广泛应用于互联网的路由协议中,例如IS-IS(Intermediate System to Intermediate System)和OSPF(Open Shortest Path First)协议。
Dijkstra算法的历史
1956年,当Edsger W. Dijkstra在阿姆斯特丹数学中心(Mathematical Center in Amsterdam)工作室,为了说明一台名为ARMAC的新电脑的计算能力,发明了最小路径问题。Dijkstra期初的目的是提出一个能被非计算机背景的人所能理解的问题,同时这一问题能被计算机有效解决。因此他对荷兰64个城市简化过的交通图(之所以是64个城市,是为了使得6比特的内存能够完全编码这些城市)计算最短路径,同时实现了Dijkstra算法的最初版本。
算法
Dijkstra算法实际上是一个贪婪算法(Greedy algorithm)。因为该算法总是试图优先访问每一步循环中距离起始点最近的下一个结点。Dijkstra算法的过程如下图所示。
下面我们分别给出算法的步骤及其正确性的证明。
初始化
- 给定图中的一个结点
s
作为起始点。 - 给定一个数组
dist[]
存储图中所有结点到s
的距离。将dist[s]
初始化为0
。对于图中的其他结点v
,初始化dist[v]
为无穷大。初始化为无穷大的意义在于我们假设其余所有结点在当前情况下尚未与s
联通。随着算法的执行,dist[v]
会保存图中从s
到v
的最短路径的距离。 - 给定一个minimum Heap,记为
Q
。堆顶为当前情况下距离s
最近的结点及相应的距离。将(s, 0)
放入堆中。 - 给定一个Set,记为
S
,保存所有已经访问过的结点。Set初始为空。基于Dijkstra算法的性质,我们总是以最短的路径遍历每一个结点,因此对于任一结点,一旦我们已经访问过,就代表着我们已经得到了从s
到达这一结点的最短路径。
计算最短路径
- 当
Q
不为空的情况下,取出堆顶的元素(v, [dist[v])
—— 也就是当前距离s
最近的结点v
,及其距离dist[v]
。 - 如果
v
在S
中,则代表我们已经访问过v
的最短路径。那么跳过当前v
,重复步骤1。 - 否则,将
v
放在S
中。 - 对于每一个与
v
相邻的结点t
:- 如果
dist[v] + weight(v, t) < dist[t]
,则更新dist[t] = dist[v] + weight(v, t)
。同时将(t, dist[t])
放进Q
中。 - 否则,不做任何处理。
- 如果
当算法结束后,dist[]
中保存图中每一个除s
之外的结点到s
的最短路径的权重值(或长度)。如果从s
到v
不存在联通的路径,则dist[v] = ∞
。
证明算法正确性
假设对于每个已经访问过的结点v
,dist[v]
存储从起始点s
到v
的最短路径。
当算法初始化时,dist[]
中只包含dist[s] = 0
,其正确性显而易见。
对于其余n-1
个结点,假设u
已经被访问且v
尚未被访问,同时u
和v
之间存在一条边u -> v
,其权重为weight(u,v)
,那么一定有dist[v] = dist[u] + weight(u, v)
。否则的话,假设存在另一条更短的路径dist[t]
满足dist[t] + weight(t, v)
,则根据上述算法,t
一定先于u
被访问,则与我们当前的假设产生了矛盾。该论断对于余下的所有结点都成立。
因此Dijkstra算法一定能给出从出发点到其余所有结点(在可以到达的情况下)的最短路径。
复杂度分析
设图中总计有E
条边,N
个结点。
时间复杂度:O(ElogE)
。因为所使用的最小堆最大可达O(E)
大小,同时我们从其中将每个元素取出来一次。
空间复杂度:O(N+E)
。其中O(N)
为存储dist
所用空间。O(E)
为存储图的邻接链表及最小堆所用空间。
Java实现
class DijkstraShortestPath {
/**
* Given a list of (source, target, weight) edge pairs, return the shortest distance from s to any
* other nodes in the graph. Any unreachable node has a distance of Integer.MAX_VALUE.
* @param edges List of tuple representation of edges containing [source, target weight].
* @param N The graph contains nodes from 1 to N.
* @param s Start node of the shortest path tree
* @return Shortest path from s to other nodes in the graph.
*/
public Map<Integer, Integer> dijkstraShortestPath(int[][] edges, int N, int s) {
Map<Integer, List<int[]>> adjList = new HashMap<>();
for (int[] edge : edges) {
adjList.computeIfAbsent(edge[0], k -> new ArrayList<>()).add(new int[]{edge[1], edge[2]});
}
Map<Integer, Integer> dist = new HashMap<>();
PriorityQueue<int[]> minHeap = new PriorityQueue<>((edge1, edge2) -> {
return edge1[1] - edge2[1];
});
minHeap.offer(new int[]{s, 0});
while (!minHeap.isEmpty()) {
int[] curr = minHeap.poll();
if (dist.containsKey(curr[0])) continue;
dist.put(curr[0], curr[1]);
if (adjList.containsKey(curr[0])) {
for (int[] edge : adjList.get(curr[0])) {
minHeap.offer(new int[]{edge[0], edge[1] + curr[1]});
}
}
}
for (int i = 1; i <= N; i++) {
if (!dist.containsKey(i)) {
dist.put(i, Integer.MAX_VALUE);
}
}
return dist;
}
}