Dijkstra算法的两种写法和时间复杂度计算
Dijkstra算法是一种单源最短路径算法,是 BFS 的延伸
基于集合的写法 (适用于稠密图)
算法
- 初始化距离向量 d d d(长度为 V V V),起点设为0,其他点设为无穷大
- 初始化集合 Q Q Q,含义为尚未确定距离的顶点的集合,将所有顶点加入
- 从 Q Q Q 中弹出距离最小的顶点 u u u
- 遍历 u u u 的所有仍在 Q Q Q 中的邻居 w w w, 判断 d [ u ] + l e n g t h ( u , w ) < d [ w ] d[u] + length(u,w) < d[w] d[u]+length(u,w)<d[w]是否成立,如果成立,更新 d [ w ] = d [ u ] + l e n g t h ( u , w ) d[w] = d[u] + length(u,w) d[w]=d[u]+length(u,w), 否则无视
- 回到 3 开始循环,直到 Q Q Q 为空集
示例代码
def dijkstra(n, edges, weights, start, end):
# Construct graph
neighbors = [[] for j in range(n)]
for j, edge in enumerate(edges):
neighbors[edge[0]].append((edge[1], weights[j]))
neighbors[edge[1]].append((edge[0], weights[j]))
# Initialize d
d = [float('inf')] * n
d[start] = 0
# Initialize Q
Q = set()
for j in range(n):
Q.add(j)
# Start loop
while Q:
# Extract-min from Q
minv = float('inf')
u = -1
for j, x in enumerate(d):
if x < minv and x in Q:
minv = x
u = j
Q.remove(u)
# Update neighbors of u
for e in neighbors[u]:
w = e[0]
weight = e[1]
if w in Q:
alt = d[u] + weight
if alt < d[w]:
d[w] = alt
return d[end]
复杂度
- 初始化 d d d 和 Q Q Q 均为 O ( V ) O(V) O(V)
- 外层循环,每次删除一个顶点,共 V V V 个顶点,故为 O ( V ) O(V) O(V)
- 内层 Extrac-min 为 O ( V ) O(V) O(V),遍历邻居为 O ( N ) O(N) O(N) ( N N N 为每个节点最大的邻居数)
综上,复杂度为 O ( V ) + O ( V 2 ) + O ( V N ) ∼ O ( V 2 ) O(V) + O(V^2) + O(VN) \sim O(V^2) O(V)+O(V2)+O(VN)∼O(V2)
基于优先队列的写法(适用于稀疏图)
算法
- 初始化距离向量 d d d(长度为 V V V),起点设为0,其他点设为无穷大
- 初始化优先队列 Q Q Q: 对所有顶点 v v v, 将 ( d [ v ] , v ) (d[v], v) (d[v],v) 推入 Q Q Q
- 初始化 v i s i t e d visited visited 为空集
- 从 Q Q Q 中弹出距离最小的顶点 u u u, 加入 v i s i t e d visited visited
- 遍历 u u u 的所有未在 v i s i t e d visited visited 中的邻居 w w w, 判断 d [ u ] + l e n g t h ( u , w ) < d [ w ] d[u] + length(u,w) < d[w] d[u]+length(u,w)<d[w]是否成立,如果成立,更新 d [ w ] = d [ u ] + l e n g t h ( u , w ) d[w] = d[u] + length(u,w) d[w]=d[u]+length(u,w) 并将 ( d [ w ] , w ) (d[w], w) (d[w],w) 推入 Q Q Q, 否则无视
- 回到 3 开始循环,直到 Q Q Q 为空集
示例代码
import heapq
def dijkstra(n, edges, weights, start, end):
# Construct graph
neighbors = [[] for j in range(n)]
for j, edge in enumerate(edges):
neighbors[edge[0]].append((edge[1], weights[j]))
neighbors[edge[1]].append((edge[0], weights[j]))
# Initialize d
d = [float('inf')] * n
d[start] = 0
# Initialize Q
Q = []
for v in range(n):
heapq.heappush(Q, (d[v], v))
# Initialize visited
visited = set()
# Start loop
while Q:
u = heapq.heappop(Q)[1]
if u in visited:
continue
visited.add(u)
for e in neighbors[u]:
w = e[0]
if w not in visited:
weight = e[1]
alt = d[u] + weight
if alt < d[w]:
d[w] = alt
heapq.heappush(Q, (d[w], w))
return d[end]
复杂度
- 初始化 d d d 为 O ( V ) O(V) O(V),初始化 Q Q Q 为 O ( V log V ) O(V\log V) O(VlogV)
- 外层循环 O ( V ) O(V) O(V)
- 内层循环 (遍历所有邻居)为 O ( N ) O(N) O(N)
- 更新邻居为 O ( log ( V ) ) O(\log(V)) O(log(V))
综上,总复杂度为 O ( V N log V ) O(VN\log V) O(VNlogV) (或写为 O ( E log V ) O(E\log V) O(ElogV))
两者比较
基于优先队列的写法节约了每次寻找距离最小顶点的开销 O ( V ) O(V) O(V),然而增加了每次更新邻居的开销 O ( log V ) O(\log V) O(logV). 当图稀疏时, V 2 ≫ E V^2 \gg E V2≫E,后者更优;当图稠密时, V 2 ∼ E V^2 \sim E V2∼E,前者更优