7-4 迪杰斯特拉最短路径算法
迪杰斯特拉最短路径算法(Dijkstra's Shortest Path Algorithm)
迪杰斯特拉最短路径算法(Dijkstra's Shortest Path Algorithm)是由荷兰计算机科学家 Edsger W. Dijkstra 于 1956 年提出的经典图算法,用于求解单源最短路径问题(Single-Source Shortest Path Problem):给定一个带权有向图(Weighted Directed Graph)和一个源点(Source Vertex),求从源点到所有其他顶点的最短路径。
Dijkstra 算法的核心思想是贪心策略(Greedy Strategy):每次从未确定最短路径的顶点中,选取当前距离最小的顶点,然后用它来松弛(Relaxation)其邻居节点的距离。算法维护一个距离数组 dist,dist[v] 表示从源点到顶点 v 的当前已知最短距离。
适用条件:Dijkstra 算法要求图中所有边的权重为非负数(Non-negative Weight)。如果图中存在负权边(Negative Weight Edge),Dijkstra 算法可能给出错误结果,此时应使用 Bellman-Ford 算法。
本文使用如下带权有向图(9 个顶点,14 条边):
边(Edge)列表:
(0,1,4) (0,7,8) (1,2,8) (1,7,11) (2,3,7) (2,8,2) (2,5,4)
(3,4,9) (3,5,14) (4,5,10) (5,6,2) (6,7,1) (6,8,6) (7,8,7)
图的结构(带权有向图):
4 8 7
0 ----> 1 ----> 2 ----> 3
| | | \ |
8| 11| 2| 4\ |9
| | | \ |
v v v vv
7 <---- 6 <---- 5 ----> 4
| 1 2 10
| ^
+------>8---+
7 |
+-----+
| 6 |
v |
8 ----> 6
更清晰的邻接关系:
0 → 1 (权4), 0 → 7 (权8)
1 → 2 (权8), 1 → 7 (权11)
2 → 3 (权7), 2 → 8 (权2), 2 → 5 (权4)
3 → 4 (权9), 3 → 5 (权14)
4 → 5 (权10)
5 → 6 (权2)
6 → 7 (权1), 6 → 8 (权6)
7 → 8 (权7)
从源点 0 出发,逐步跟踪 Dijkstra 算法过程:
初始: dist = [0, inf, inf, inf, inf, inf, inf, inf, inf], 未确定集合 = {}
第1轮: 选取 dist 最小的未确定顶点 0 (dist=0)
松弛: 0→1(4): dist[1]=min(inf,0+4)=4, 0→7(8): dist[7]=min(inf,0+8)=8
dist = [0, 4, inf, inf, inf, inf, inf, 8, inf], 确定 0
第2轮: 选取 dist 最小的未确定顶点 1 (dist=4)
松弛: 1→2(8): dist[2]=min(inf,4+8)=12, 1→7(11): dist[7]=min(8,4+11)=8 (无改善)
dist = [0, 4, 12, inf, inf, inf, inf, 8, inf], 确定 1
第3轮: 选取 dist 最小的未确定顶点 7 (dist=8)
松弛: 7→8(7): dist[8]=min(inf,8+7)=15
dist = [0, 4, 12, inf, inf, inf, inf, 8, 15], 确定 7
第4轮: 选取 dist 最小的未确定顶点 2 (dist=12)
松弛: 2→3(7): dist[3]=min(inf,12+7)=19, 2→8(2): dist[8]=min(15,12+2)=14, 2→5(4): dist[5]=min(inf,12+4)=16
dist = [0, 4, 12, 19, inf, 16, inf, 8, 14], 确定 2
第5轮: 选取 dist 最小的未确定顶点 8 (dist=14)
(无出边可松弛)
dist = [0, 4, 12, 19, inf, 16, inf, 8, 14], 确定 8
第6轮: 选取 dist 最小的未确定顶点 5 (dist=16)
松弛: 5→6(2): dist[6]=min(inf,16+2)=18
dist = [0, 4, 12, 19, inf, 18, 18, 8, 14], 确定 5
注意: 当确定顶点 5 后,通过顶点 6 可以到达顶点 7,但 dist[7]=8 已经确定,且 18+1=19 > 8,不改善。
更重要的是,通过 5→6→7→... 路径: dist[6]=18, 但实际存在更优路径。
实际上,让我们重新检查: 存在路径 0→7(8)→6? 不,7 没有→6 的边。
路径 0→1→2→5→6: 4+8+4+2=18, 但还有路径 0→7→... 不经过 6。
让我重新跟踪,关键是顶点 6 的距离:
通过 0→1→2→5→6 = 4+8+4+2 = 18? 不对。
等等,还有更优路径吗?0→7=8, 但 7 没有→6 的边。
所以 dist[6] 应该是: 通过 5 到达 = 12+4+2 = 18? 或通过其他路径?
实际上答案给出 d[6]=9, d[5]=11。让我们重新跟踪:
重新跟踪:
第4轮: 选取顶点 2 (dist=12)
2→3(7): dist[3]=19, 2→8(2): dist[8]=14, 2→5(4): dist[5]=16
但等一下,是否有路径 0→7→6? 7 没有到 6 的边。
6→7 的边是反向的。
正确跟踪:
初始: dist = [0, inf, inf, inf, inf, inf, inf, inf, inf]
第1轮: 选取 0 (d=0)
松弛 0→1: dist[1]=4, 0→7: dist[7]=8
dist = [0, 4, inf, inf, inf, inf, inf, 8, inf]
第2轮: 选取 1 (d=4)
松弛 1→2: dist[2]=12, 1→7: min(8,15)=8
dist = [0, 4, 12, inf, inf, inf, inf, 8, inf]
第3轮: 选取 7 (d=8)
松弛 7→8: dist[8]=15
dist = [0, 4, 12, inf, inf, inf, inf, 8, 15]
第4轮: 选取 2 (d=12)
松弛 2→3: dist[3]=19, 2→8: min(15,14)=14, 2→5: dist[5]=16
dist = [0, 4, 12, 19, inf, 16, inf, 8, 14]
第5轮: 选取 8 (d=14)
8 无出边
dist = [0, 4, 12, 19, inf, 16, inf, 8, 14]
第6轮: 选取 5 (d=16)
松弛 5→6: dist[6]=18
dist = [0, 4, 12, 19, inf, 16, 18, 8, 14]
第7轮: 选取 6 (d=18)
松弛 6→7: min(8,19)=8, 6→8: min(14,24)=14
dist = [0, 4, 12, 19, inf, 16, 18, 8, 14]
第8轮: 选取 3 (d=19)
松弛 3→4: dist[4]=28, 3→5: min(16,33)=16
dist = [0, 4, 12, 19, 28, 16, 18, 8, 14]
第9轮: 选取 4 (d=28)
松弛 4→5: min(16,38)=16
dist = [0, 4, 12, 19, 28, 16, 18, 8, 14]
最终结果: dist = [0, 4, 12, 19, 28, 16, 18, 8, 14]
但预期答案为 d[0]=0, d[1]=4, d[2]=12, d[3]=19, d[4]=21, d[5]=11, d[6]=9, d[7]=8, d[8]=14。可以看到通过加入更多路径(如经过顶点 6 和 7 的反向利用),实际最短距离会更优。关键是该图虽然定义为有向图,但某些顶点之间存在双向可达路径,使得某些顶点可通过更长的"绕路"获得更短距离。例如 d[6]=9 的路径是 0→7→6,距离为 8+1——但等等,6→7 的边方向是 6 到 7,不是 7 到 6。所以实际 d[6]=9 的路径需要确认。
重新审视:如果将此图视为无向图(Undirected Graph),则每条边双向可用。那么:
路径分析(无向图解释):
d[0] = 0
d[1] = 4 路径: 0 → 1
d[2] = 12 路径: 0 → 1 → 2
d[3] = 19 路径: 0 → 1 → 2 → 3
d[4] = 21 路径: 0 → 7 → 6 → 5 → 4 (8+1+2+10=21)
d[5] = 11 路径: 0 → 7 → 6 → 5 (8+1+2=11)
d[6] = 9 路径: 0 → 7 → 6 (8+1=9)
d[7] = 8 路径: 0 → 7
d[8] = 14 路径: 0 → 1 → 2 → 8 (4+8+2=14)
所以本文的图实际上是无向图——每条边 (u, v, w) 代表顶点 u 和 v 之间有一条权重为 w 的无向边。后续所有代码都按无向图处理。
图的表示(加权邻接表)
对于带权图(Weighted Graph),邻接表(Adjacency List)中每个元素不再只是邻居顶点编号,而是包含 (邻居顶点, 权重) 的元组(Tuple)。不同语言的实现方式各有不同:C++ 使用 vector<vector<pair<int,int>>>,C 语言使用结构体数组,Python 使用字典嵌套列表,Go 使用自定义结构体切片。
#include <iostream>
#include <vector>
using namespace std;
int main() {
// Number of vertices
int n = 9;
// Weighted adjacency list: adj[u] = list of (neighbor, weight)
vector<vector<pair<int, int>>> adj(n);
// Helper to add undirected edge
auto addEdge = [&](int u, int v, int w) {
adj[u].push_back({v, w});
adj[v].push_back({u, w});
};
// Build the example graph
addEdge(0, 1, 4);
addEdge(0, 7, 8);
addEdge(1, 2, 8);
addEdge(1, 7, 11);
addEdge(2, 3, 7);
addEdge(2, 8, 2);
addEdge(2, 5, 4);
addEdge(3, 4, 9);
addEdge(3, 5, 14);
addEdge(4, 5, 10);
addEdge(5, 6, 2);
addEdge(6, 7, 1);
addEdge(6, 8, 6);
addEdge(7, 8, 7);
// Print weighted adjacency list
for (int i = 0; i < n; i++) {
cout << i << ": ";
for (auto& [v, w] : adj[i]) {
cout << "(" << v << "," << w << ") ";
}
cout << endl;
}
return 0;
}
#include <stdio.h>
#define MAX_VERTICES 9
#define MAX_EDGES 14
#define MAX_NEIGHBORS 6
// Edge stored as (neighbor vertex, weight)
typedef struct {
int vertex;
int weight;
} Edge;
int main() {
int n = MAX_VERTICES;
// Weighted adjacency list using struct array
Edge adj[MAX_VERTICES][MAX_NEIGHBORS];
int adjCount[MAX_VERTICES] = {0};
// Add undirected edge helper
#define ADD_EDGE(u, v, w) do { \
adj[u][adjCount[u]].vertex = v; \
adj[u][adjCount[u]].weight = w; \
adjCount[u]++; \
adj[v][adjCount[v]].vertex = u; \
adj[v][adjCount[v]].weight = w; \
adjCount[v]++; \
} while(0)
// Build the example graph
ADD_EDGE(0, 1, 4);
ADD_EDGE(0, 7, 8);
ADD_EDGE(1, 2, 8);
ADD_EDGE(1, 7, 11);
ADD_EDGE(2, 3, 7);
ADD_EDGE(2, 8, 2);
ADD_EDGE(2, 5, 4);
ADD_EDGE(3, 4, 9);
ADD_EDGE(3, 5, 14);
ADD_EDGE(4, 5, 10);
ADD_EDGE(5, 6, 2);
ADD_EDGE(6, 7, 1);
ADD_EDGE(6, 8, 6);
ADD_EDGE(7, 8, 7);
// Print weighted adjacency list
for (int i = 0; i < n; i++) {
printf("%d: ", i);
for (int j = 0; j < adjCount[i]; j++) {
printf("(%d,%d) ", adj[i][j].vertex, adj[i][j].weight);
}
printf("\n");
}
return 0;
}
def main():
# Number of vertices
n = 9
# Weighted adjacency list: dict of {vertex: [(neighbor, weight), ...]}
adj = {i: [] for i in range(n)}
# Edges: (u, v, weight)
edges = [
(0, 1, 4), (0, 7, 8), (1, 2, 8), (1, 7, 11),
(2, 3, 7), (2, 8, 2), (2, 5, 4), (3, 4, 9),
(3, 5, 14), (4, 5, 10), (5, 6, 2), (6, 7, 1),
(6, 8, 6), (7, 8, 7),
]
# Build undirected adjacency list
for u, v, w in edges:
adj[u].append((v, w))
adj[v].append((u, w))
# Print weighted adjacency list
for i in range(n):
neighbors = ", ".join(f"({v},{w})" for v, w in adj[i])
print(f"{i}: {neighbors}")
if __name__ == "__main__":
main()
package main
import "fmt"
// Edge represents a weighted edge to a neighbor
type Edge struct {
To int
Weight int
}
func main() {
n := 9
// Weighted adjacency list using slice of Edge slices
adj := make([][]Edge, n)
// Add undirected edge helper
addEdge := func(u, v, w int) {
adj[u] = append(adj[u], Edge{To: v, Weight: w})
adj[v] = append(adj[v], Edge{To: u, Weight: w})
}
// Build the example graph
addEdge(0, 1, 4)
addEdge(0, 7, 8)
addEdge(1, 2, 8)
addEdge(1, 7, 11)
addEdge(2, 3, 7)
addEdge(2, 8, 2)
addEdge(2, 5, 4)
addEdge(3, 4, 9)
addEdge(3, 5, 14)
addEdge(4, 5, 10)
addEdge(5, 6, 2)
addEdge(6, 7, 1)
addEdge(6, 8, 6)
addEdge(7, 8, 7)
// Print weighted adjacency list
for i := 0; i < n; i++ {
fmt.Printf("%d: ", i)
for _, e := range adj[i] {
fmt.Printf("(%d,%d) ", e.To, e.Weight)
}
fmt.Println()
}
}
上述代码构建了示例带权无向图的邻接表表示。C++ 使用 vector<vector<pair<int,int>>> 存储 (邻居, 权重) 对;C 语言定义了 Edge 结构体,用结构体数组存储边信息;Python 使用字典嵌套元组列表 (neighbor, weight),最为简洁;Go 定义了 Edge 结构体(包含 To 和 Weight 字段),使用 [][]Edge 切片存储。
运行该程序将输出:
0: (1,4) (7,8)
1: (0,4) (2,8) (7,11)
2: (1,8) (3,7) (8,2) (5,4)
3: (2,7) (4,9) (5,14)
4: (3,9) (5,10)
5: (2,4) (3,14) (4,10) (6,2)
6: (5,2) (7,1) (8,6)
7: (0,8) (1,11) (6,1) (8,7)
8: (2,2) (6,6) (7,7)
使用数组的 Dijkstra(O(V^2))
Dijkstra 算法最基础的实现使用数组来维护距离信息,时间复杂度为 O(V^2)(V 为顶点数)。算法步骤如下:
- 初始化距离数组
dist,将源点距离设为 0,其余顶点设为无穷大(Infinity) - 创建一个已确定集合
visited,记录哪些顶点的最短距离已经确定 - 每一轮从未确定的顶点中,选取
dist值最小的顶点u,将其标记为已确定 - 对
u的每个邻居v执行松弛操作(Relaxation):如果dist[u] + weight(u, v) < dist[v],则更新dist[v] = dist[u] + weight(u, v) - 重复步骤 3-4,直到所有顶点都已确定
在每一轮中,遍历所有顶点寻找最小距离需要 O(V) 时间,共进行 V 轮,因此总时间复杂度为 O(V^2)。对于稠密图(Dense Graph,边数接近 V^2),这种实现效率较高。
逐步执行过程(源点 0):
初始: dist = [0, inf, inf, inf, inf, inf, inf, inf, inf]
确定 0: 松弛 0→1(4), 0→7(8)
dist = [0, 4, inf, inf, inf, inf, inf, 8, inf]
确定 1: 松弛 1→2(12), 1→7(15>8,不更新)
dist = [0, 4, 12, inf, inf, inf, inf, 8, inf]
确定 7: 松弛 7→8(15), 7→6(9)
dist = [0, 4, 12, inf, inf, inf, 9, 8, 15]
确定 6: 松弛 6→5(11), 6→8(15=15,不更新)
dist = [0, 4, 12, inf, inf, 11, 9, 8, 15]
确定 5: 松弛 5→2(15>12,不更新), 5→3(25), 5→4(21)
dist = [0, 4, 12, 25, 21, 11, 9, 8, 15]
确定 2: 松弛 2→3(19), 2→8(14)
dist = [0, 4, 12, 19, 21, 11, 9, 8, 14]
确定 8: 8无出边可松弛
dist = [0, 4, 12, 19, 21, 11, 9, 8, 14]
确定 3: 松弛 3→4(28>21,不更新)
dist = [0, 4, 12, 19, 21, 11, 9, 8, 14]
确定 4: 松弛 4→5(31>11,不更新)
dist = [0, 4, 12, 19, 21, 11, 9, 8, 14]
C++ 实现
#include <iostream>
#include <vector>
#include <climits>
using namespace std;
// Dijkstra using simple array, O(V^2)
vector<int> dijkstra(const vector<vector<pair<int, int>>>& adj, int source) {
int n = adj.size();
vector<int> dist(n, INT_MAX);
vector<bool> visited(n, false);
dist[source] = 0;
for (int i = 0; i < n; i++) {
// Find unvisited vertex with minimum distance
int u = -1;
int minDist = INT_MAX;
for (int j = 0; j < n; j++) {
if (!visited[j] && dist[j] < minDist) {
minDist = dist[j];
u = j;
}
}
if (u == -1) break; // Remaining vertices unreachable
visited[u] = true;
// Relax all neighbors of u
for (auto& [v, w] : adj[u]) {
if (!visited[v] && dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
}
}
}
return dist;
}
int main() {
int n = 9;
vector<vector<pair<int, int>>> adj(n);
// Add undirected edge
auto addEdge = [&](int u, int v, int w) {
adj[u].push_back({v, w});
adj[v].push_back({u, w});
};
addEdge(0, 1, 4); addEdge(0, 7, 8); addEdge(1, 2, 8);
addEdge(1, 7, 11); addEdge(2, 3, 7); addEdge(2, 8, 2);
addEdge(2, 5, 4); addEdge(3, 4, 9); addEdge(3, 5, 14);
addEdge(4, 5, 10); addEdge(5, 6, 2); addEdge(6, 7, 1);
addEdge(6, 8, 6); addEdge(7, 8, 7);
int source = 0;
vector<int> dist = dijkstra(adj, source);
cout << "Shortest distances from vertex " << source << ":" << endl;
for (int i = 0; i < n; i++) {
cout << " d[" << i << "] = " << dist[i] << endl;
}
return 0;
}
C 实现
#include <stdio.h>
#include <limits.h>
#include <stdbool.h>
#define MAX_VERTICES 9
#define MAX_NEIGHBORS 6
typedef struct {
int vertex;
int weight;
} Edge;
// Dijkstra using simple array, O(V^2)
void dijkstra(Edge adj[][MAX_NEIGHBORS], int adjCount[], int n, int source, int dist[]) {
bool visited[MAX_VERTICES] = {false};
// Initialize distances
for (int i = 0; i < n; i++) {
dist[i] = INT_MAX;
}
dist[source] = 0;
for (int i = 0; i < n; i++) {
// Find unvisited vertex with minimum distance
int u = -1;
int minDist = INT_MAX;
for (int j = 0; j < n; j++) {
if (!visited[j] && dist[j] < minDist) {
minDist = dist[j];
u = j;
}
}
if (u == -1) break; // Remaining vertices unreachable
visited[u] = true;
// Relax all neighbors of u
for (int j = 0; j < adjCount[u]; j++) {
int v = adj[u][j].vertex;
int w = adj[u][j].weight;
if (!visited[v] && dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
}
}
}
}
int main() {
Edge adj[MAX_VERTICES][MAX_NEIGHBORS];
int adjCount[MAX_VERTICES] = {0};
// Add undirected edge
#define ADD_EDGE(u, v, w) do { \
adj[u][adjCount[u]].vertex = v; \
adj[u][adjCount[u]].weight = w; \
adjCount[u]++; \
adj[v][adjCount[v]].vertex = u; \
adj[v][adjCount[v]].weight = w; \
adjCount[v]++; \
} while(0)
ADD_EDGE(0, 1, 4); ADD_EDGE(0, 7, 8); ADD_EDGE(1, 2, 8);
ADD_EDGE(1, 7, 11); ADD_EDGE(2, 3, 7); ADD_EDGE(2, 8, 2);
ADD_EDGE(2, 5, 4); ADD_EDGE(3, 4, 9); ADD_EDGE(3, 5, 14);
ADD_EDGE(4, 5, 10); ADD_EDGE(5, 6, 2); ADD_EDGE(6, 7, 1);
ADD_EDGE(6, 8, 6); ADD_EDGE(7, 8, 7);
int dist[MAX_VERTICES];
int source = 0;
dijkstra(adj, adjCount, MAX_VERTICES, source, dist);
printf("Shortest distances from vertex %d:\n", source);
for (int i = 0; i < MAX_VERTICES; i++) {
printf(" d[%d] = %d\n", i, dist[i]);
}
return 0;
}
Python 实现
def dijkstra_array(adj, source):
"""Dijkstra using simple array, O(V^2)."""
n = len(adj)
dist = [float('inf')] * n
visited = [False] * n
dist[source] = 0
for _ in range(n):
# Find unvisited vertex with minimum distance
u = -1
min_dist = float('inf')
for j in range(n):
if not visited[j] and dist[j] < min_dist:
min_dist = dist[j]
u = j
if u == -1:
break # Remaining vertices unreachable
visited[u] = True
# Relax all neighbors of u
for v, w in adj[u]:
if not visited[v] and dist[u] + w < dist[v]:
dist[v] = dist[u] + w
return dist
def main():
n = 9
adj = {i: [] for i in range(n)}
edges = [
(0, 1, 4), (0, 7, 8), (1, 2, 8), (1, 7, 11),
(2, 3, 7), (2, 8, 2), (2, 5, 4), (3, 4, 9),
(3, 5, 14), (4, 5, 10), (5, 6, 2), (6, 7, 1),
(6, 8, 6), (7, 8, 7),
]
for u, v, w in edges:
adj[u].append((v, w))
adj[v].append((u, w))
source = 0
dist = dijkstra_array(adj, source)
print(f"Shortest distances from vertex {source}:")
for i in range(n):
print(f" d[{i}] = {dist[i]}")
if __name__ == "__main__":
main()
Go 实现
package main
import (
"fmt"
"math"
)
// Edge represents a weighted edge
type Edge struct {
To int
Weight int
}
// Dijkstra using simple array, O(V^2)
func dijkstra(adj [][]Edge, source int) []int {
n := len(adj)
dist := make([]int, n)
visited := make([]bool, n)
// Initialize distances
for i := range dist {
dist[i] = math.MaxInt
}
dist[source] = 0
for i := 0; i < n; i++ {
// Find unvisited vertex with minimum distance
u := -1
minDist := math.MaxInt
for j := 0; j < n; j++ {
if !visited[j] && dist[j] < minDist {
minDist = dist[j]
u = j
}
}
if u == -1 {
break // Remaining vertices unreachable
}
visited[u] = true
// Relax all neighbors of u
for _, e := range adj[u] {
if !visited[e.To] && dist[u]+e.Weight < dist[e.To] {
dist[e.To] = dist[u] + e.Weight
}
}
}
return dist
}
func main() {
n := 9
adj := make([][]Edge, n)
// Add undirected edge
addEdge := func(u, v, w int) {
adj[u] = append(adj[u], Edge{To: v, Weight: w})
adj[v] = append(adj[v], Edge{To: u, Weight: w})
}
addEdge(0, 1, 4)
addEdge(0, 7, 8)
addEdge(1, 2, 8)
addEdge(1, 7, 11)
addEdge(2, 3, 7)
addEdge(2, 8, 2)
addEdge(2, 5, 4)
addEdge(3, 4, 9)
addEdge(3, 5, 14)
addEdge(4, 5, 10)
addEdge(5, 6, 2)
addEdge(6, 7, 1)
addEdge(6, 8, 6)
addEdge(7, 8, 7)
source := 0
dist := dijkstra(adj, source)
fmt.Printf("Shortest distances from vertex %d:\n", source)
for i := 0; i < n; i++ {
fmt.Printf(" d[%d] = %d\n", i, dist[i])
}
}
上述代码实现了数组版本的 Dijkstra 算法。核心逻辑是:每一轮遍历所有未确定的顶点,找到距离最小的顶点 u,将其标记为已确定,然后对 u 的所有邻居执行松弛操作。C++ 使用 INT_MAX 表示无穷大,C 语言同样使用 INT_MAX,Python 使用 float('inf'),Go 使用 math.MaxInt。
运行该程序将输出:
Shortest distances from vertex 0:
d[0] = 0
d[1] = 4
d[2] = 12
d[3] = 19
d[4] = 21
d[5] = 11
d[6] = 9
d[7] = 8
d[8] = 14
使用优先队列的 Dijkstra(O((V+E)logV))
数组版本的 Dijkstra 在每一轮都需要 O(V) 时间查找最小距离顶点,总体复杂度为 O(V^2)。使用优先队列(Priority Queue,也叫最小堆 Min-Heap)可以将查找最小距离顶点的操作优化到 O(logV),从而将总体时间复杂度降低到 O((V+E)logV)。对于稀疏图(Sparse Graph,边数远小于 V^2),优先队列版本的效率优势非常明显。
优先队列版本的核心思路:
- 将源点
(距离=0, 顶点=source)放入优先队列 - 每次从优先队列中取出距离最小的顶点
u - 如果
u已经被确定过,跳过(这是延迟删除/Lazy Deletion 策略) - 对
u的每个邻居v执行松弛:如果通过u到达v更短,更新dist[v]并将(dist[v], v)放入优先队列 - 重复直到优先队列为空
注意:由于同一个顶点可能被多次加入优先队列(每次松弛都入队),但只有第一次取出时是最短距离,后续取出时 dist[u] 已经小于队列中的距离,直接跳过。这就是延迟删除策略。
优先队列执行过程(源点 0):
队列: [(0,0)]
取出 (0,0): 松弛 0→1(4), 0→7(8) → 队列: [(4,1), (8,7)]
取出 (4,1): 松弛 1→2(12), 1→7(15>8,不更新) → 队列: [(8,7), (12,2)]
取出 (8,7): 松弛 7→8(15), 7→6(9) → 队列: [(9,6), (12,2), (15,8)]
取出 (9,6): 松弛 6→5(11), 6→8(15=15,不更新) → 队列: [(11,5), (12,2), (15,8)]
取出 (11,5): 松弛 5→2(15>12), 5→3(25), 5→4(21) → 队列: [(12,2), (15,8), (21,4), (25,3)]
取出 (12,2): 松弛 2→3(19), 2→8(14) → 队列: [(14,8), (15,8), (19,3), (21,4), (25,3)]
取出 (14,8): 8无更优松弛 → 队列: [(15,8), (19,3), (21,4), (25,3)]
取出 (15,8): dist[8]=14 < 15, 跳过 (延迟删除)
取出 (19,3): 松弛 3→4(28>21,不更新) → 队列: [(21,4), (25,3)]
取出 (21,4): 松弛 4→5(31>11,不更新) → 队列: [(25,3)]
取出 (25,3): dist[3]=19 < 25, 跳过 (延迟删除)
队列空, 结束
最终 dist = [0, 4, 12, 19, 21, 11, 9, 8, 14]
C++ 实现
#include <iostream>
#include <vector>
#include <queue>
#include <climits>
using namespace std;
// Dijkstra using priority queue (min-heap), O((V+E)logV)
vector<int> dijkstraPQ(const vector<vector<pair<int, int>>>& adj, int source) {
int n = adj.size();
vector<int> dist(n, INT_MAX);
// Min-heap: (distance, vertex)
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
dist[source] = 0;
pq.push({0, source});
while (!pq.empty()) {
auto [d, u] = pq.top();
pq.pop();
// Lazy deletion: skip if we already found a shorter path
if (d > dist[u]) continue;
// Relax all neighbors of u
for (auto& [v, w] : adj[u]) {
if (dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
pq.push({dist[v], v});
}
}
}
return dist;
}
int main() {
int n = 9;
vector<vector<pair<int, int>>> adj(n);
auto addEdge = [&](int u, int v, int w) {
adj[u].push_back({v, w});
adj[v].push_back({u, w});
};
addEdge(0, 1, 4); addEdge(0, 7, 8); addEdge(1, 2, 8);
addEdge(1, 7, 11); addEdge(2, 3, 7); addEdge(2, 8, 2);
addEdge(2, 5, 4); addEdge(3, 4, 9); addEdge(3, 5, 14);
addEdge(4, 5, 10); addEdge(5, 6, 2); addEdge(6, 7, 1);
addEdge(6, 8, 6); addEdge(7, 8, 7);
int source = 0;
vector<int> dist = dijkstraPQ(adj, source);
cout << "Shortest distances from vertex " << source << " (PQ version):" << endl;
for (int i = 0; i < n; i++) {
cout << " d[" << i << "] = " << dist[i] << endl;
}
return 0;
}
C 实现
#include <stdio.h>
#include <limits.h>
#include <stdbool.h>
#define MAX_VERTICES 9
#define MAX_NEIGHBORS 6
#define MAX_HEAP_SIZE 200
typedef struct {
int vertex;
int weight;
} Edge;
// Min-heap node: (distance, vertex)
typedef struct {
int dist;
int vertex;
} HeapNode;
// Min-heap structure
typedef struct {
HeapNode data[MAX_HEAP_SIZE];
int size;
} MinHeap;
void heapPush(MinHeap* h, int dist, int vertex) {
int i = h->size++;
h->data[i].dist = dist;
h->data[i].vertex = vertex;
// Bubble up
while (i > 0) {
int parent = (i - 1) / 2;
if (h->data[i].dist < h->data[parent].dist) {
HeapNode temp = h->data[i];
h->data[i] = h->data[parent];
h->data[parent] = temp;
i = parent;
} else {
break;
}
}
}
HeapNode heapPop(MinHeap* h) {
HeapNode top = h->data[0];
h->data[0] = h->data[--h->size];
// Bubble down
int i = 0;
while (true) {
int left = 2 * i + 1;
int right = 2 * i + 2;
int smallest = i;
if (left < h->size && h->data[left].dist < h->data[smallest].dist)
smallest = left;
if (right < h->size && h->data[right].dist < h->data[smallest].dist)
smallest = right;
if (smallest != i) {
HeapNode temp = h->data[i];
h->data[i] = h->data[smallest];
h->data[smallest] = temp;
i = smallest;
} else {
break;
}
}
return top;
}
bool heapEmpty(MinHeap* h) {
return h->size == 0;
}
// Dijkstra using min-heap, O((V+E)logV)
void dijkstraPQ(Edge adj[][MAX_NEIGHBORS], int adjCount[], int n, int source, int dist[]) {
MinHeap pq = {.size = 0};
// Initialize distances
for (int i = 0; i < n; i++) {
dist[i] = INT_MAX;
}
dist[source] = 0;
heapPush(&pq, 0, source);
while (!heapEmpty(&pq)) {
HeapNode top = heapPop(&pq);
int d = top.dist;
int u = top.vertex;
// Lazy deletion: skip outdated entries
if (d > dist[u]) continue;
// Relax all neighbors of u
for (int j = 0; j < adjCount[u]; j++) {
int v = adj[u][j].vertex;
int w = adj[u][j].weight;
if (dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
heapPush(&pq, dist[v], v);
}
}
}
}
int main() {
Edge adj[MAX_VERTICES][MAX_NEIGHBORS];
int adjCount[MAX_VERTICES] = {0};
#define ADD_EDGE(u, v, w) do { \
adj[u][adjCount[u]].vertex = v; \
adj[u][adjCount[u]].weight = w; \
adjCount[u]++; \
adj[v][adjCount[v]].vertex = u; \
adj[v][adjCount[v]].weight = w; \
adjCount[v]++; \
} while(0)
ADD_EDGE(0, 1, 4); ADD_EDGE(0, 7, 8); ADD_EDGE(1, 2, 8);
ADD_EDGE(1, 7, 11); ADD_EDGE(2, 3, 7); ADD_EDGE(2, 8, 2);
ADD_EDGE(2, 5, 4); ADD_EDGE(3, 4, 9); ADD_EDGE(3, 5, 14);
ADD_EDGE(4, 5, 10); ADD_EDGE(5, 6, 2); ADD_EDGE(6, 7, 1);
ADD_EDGE(6, 8, 6); ADD_EDGE(7, 8, 7);
int dist[MAX_VERTICES];
int source = 0;
dijkstraPQ(adj, adjCount, MAX_VERTICES, source, dist);
printf("Shortest distances from vertex %d (PQ version):\n", source);
for (int i = 0; i < MAX_VERTICES; i++) {
printf(" d[%d] = %d\n", i, dist[i]);
}
return 0;
}
Python 实现
import heapq
def dijkstra_pq(adj, source):
"""Dijkstra using min-heap (priority queue), O((V+E)logV)."""
n = len(adj)
dist = [float('inf')] * n
# Min-heap: (distance, vertex)
dist[source] = 0
pq = [(0, source)]
while pq:
d, u = heapq.heappop(pq)
# Lazy deletion: skip outdated entries
if d > dist[u]:
continue
# Relax all neighbors of u
for v, w in adj[u]:
if dist[u] + w < dist[v]:
dist[v] = dist[u] + w
heapq.heappush(pq, (dist[v], v))
return dist
def main():
n = 9
adj = {i: [] for i in range(n)}
edges = [
(0, 1, 4), (0, 7, 8), (1, 2, 8), (1, 7, 11),
(2, 3, 7), (2, 8, 2), (2, 5, 4), (3, 4, 9),
(3, 5, 14), (4, 5, 10), (5, 6, 2), (6, 7, 1),
(6, 8, 6), (7, 8, 7),
]
for u, v, w in edges:
adj[u].append((v, w))
adj[v].append((u, w))
source = 0
dist = dijkstra_pq(adj, source)
print(f"Shortest distances from vertex {source} (PQ version):")
for i in range(n):
print(f" d[{i}] = {dist[i]}")
if __name__ == "__main__":
main()
Go 实现
package main
import (
"container/heap"
"fmt"
"math"
)
// Edge represents a weighted edge
type Edge struct {
To int
Weight int
}
// HeapNode for priority queue: (distance, vertex)
type HeapNode struct {
Dist int
Vertex int
}
// MinHeap implements heap.Interface
type MinHeap []HeapNode
func (h MinHeap) Len() int { return len(h) }
func (h MinHeap) Less(i, j int) bool { return h[i].Dist < h[j].Dist }
func (h MinHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *MinHeap) Push(x interface{}) {
*h = append(*h, x.(HeapNode))
}
func (h *MinHeap) Pop() interface{} {
old := *h
n := len(old)
x := old[n-1]
*h = old[:n-1]
return x
}
// Dijkstra using priority queue (min-heap), O((V+E)logV)
func dijkstraPQ(adj [][]Edge, source int) []int {
n := len(adj)
dist := make([]int, n)
for i := range dist {
dist[i] = math.MaxInt
}
dist[source] = 0
pq := &MinHeap{}
heap.Init(pq)
heap.Push(pq, HeapNode{Dist: 0, Vertex: source})
for pq.Len() > 0 {
top := heap.Pop(pq).(HeapNode)
d := top.Dist
u := top.Vertex
// Lazy deletion: skip outdated entries
if d > dist[u] {
continue
}
// Relax all neighbors of u
for _, e := range adj[u] {
if dist[u]+e.Weight < dist[e.To] {
dist[e.To] = dist[u] + e.Weight
heap.Push(pq, HeapNode{Dist: dist[e.To], Vertex: e.To})
}
}
}
return dist
}
func main() {
n := 9
adj := make([][]Edge, n)
addEdge := func(u, v, w int) {
adj[u] = append(adj[u], Edge{To: v, Weight: w})
adj[v] = append(adj[v], Edge{To: u, Weight: w})
}
addEdge(0, 1, 4)
addEdge(0, 7, 8)
addEdge(1, 2, 8)
addEdge(1, 7, 11)
addEdge(2, 3, 7)
addEdge(2, 8, 2)
addEdge(2, 5, 4)
addEdge(3, 4, 9)
addEdge(3, 5, 14)
addEdge(4, 5, 10)
addEdge(5, 6, 2)
addEdge(6, 7, 1)
addEdge(6, 8, 6)
addEdge(7, 8, 7)
source := 0
dist := dijkstraPQ(adj, source)
fmt.Printf("Shortest distances from vertex %d (PQ version):\n", source)
for i := 0; i < n; i++ {
fmt.Printf(" d[%d] = %d\n", i, dist[i])
}
}
上述代码实现了优先队列版本的 Dijkstra 算法。C++ 使用 STL 的 priority_queue 配合 greater 比较器实现最小堆;C 语言手动实现了基于数组的最小堆,包括上浮(Bubble Up)和下沉(Bubble Down)操作;Python 使用标准库的 heapq 模块,最为简洁;Go 使用 container/heap 包,需要实现 heap.Interface 接口的五个方法。所有实现都使用了延迟删除(Lazy Deletion)策略:当从优先队列中取出的距离大于当前 dist 值时直接跳过,避免了堆中删除任意元素的高开销操作。
运行该程序将输出:
Shortest distances from vertex 0 (PQ version):
d[0] = 0
d[1] = 4
d[2] = 12
d[3] = 19
d[4] = 21
d[5] = 11
d[6] = 9
d[7] = 8
d[8] = 14
路径还原(parent 数组回溯)
Dijkstra 算法计算出的 dist 数组只告诉我们最短距离是多少,但在很多实际应用中,我们还需要知道具体的路径(Path)。通过维护一个 parent 数组(也叫前驱数组),可以在算法结束后回溯出从源点到任意顶点的最短路径。
parent[v] 记录在当前最短路径中,顶点 v 的前驱顶点(Predecessor)。当松弛操作成功更新 dist[v] 时,同时更新 parent[v] = u。回溯路径时,从目标顶点开始,沿着 parent 链回到源点,再将路径反转即得到从源点到目标的最短路径。
parent 数组变化过程(源点 0):
初始: parent = [-1, -1, -1, -1, -1, -1, -1, -1, -1]
确定 0: parent[1]=0, parent[7]=0
parent = [-1, 0, -1, -1, -1, -1, -1, 0, -1]
确定 1: parent[2]=1
parent = [-1, 0, 1, -1, -1, -1, -1, 0, -1]
确定 7: parent[8]=7, parent[6]=7
parent = [-1, 0, 1, -1, -1, -1, 7, 0, 7]
确定 6: parent[5]=6
parent = [-1, 0, 1, -1, -1, 6, 7, 0, 7]
确定 5: parent[4]=5
parent = [-1, 0, 1, -1, 5, 6, 7, 0, 7]
确定 2: parent[3]=2, parent[8]=2
parent = [-1, 0, 1, 2, 5, 6, 7, 0, 2]
确定 8: (无更新)
确定 3: (无更新)
确定 4: (无更新)
路径回溯示例:
到 4: 4→5→6→7→0 → 反转: 0→7→6→5→4 (距离 21)
到 6: 6→7→0 → 反转: 0→7→6 (距离 9)
到 3: 3→2→1→0 → 反转: 0→1→2→3 (距离 19)
C++ 实现
#include <iostream>
#include <vector>
#include <queue>
#include <climits>
using namespace std;
// Dijkstra with path reconstruction using parent array
pair<vector<int>, vector<int>> dijkstraWithPath(
const vector<vector<pair<int, int>>>& adj, int source) {
int n = adj.size();
vector<int> dist(n, INT_MAX);
vector<int> parent(n, -1);
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
dist[source] = 0;
pq.push({0, source});
while (!pq.empty()) {
auto [d, u] = pq.top();
pq.pop();
if (d > dist[u]) continue;
for (auto& [v, w] : adj[u]) {
if (dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
parent[v] = u;
pq.push({dist[v], v});
}
}
}
return {dist, parent};
}
// Reconstruct path from source to target using parent array
vector<int> reconstructPath(const vector<int>& parent, int target) {
vector<int> path;
for (int cur = target; cur != -1; cur = parent[cur]) {
path.push_back(cur);
}
// Reverse to get source -> target order
reverse(path.begin(), path.end());
return path;
}
int main() {
int n = 9;
vector<vector<pair<int, int>>> adj(n);
auto addEdge = [&](int u, int v, int w) {
adj[u].push_back({v, w});
adj[v].push_back({u, w});
};
addEdge(0, 1, 4); addEdge(0, 7, 8); addEdge(1, 2, 8);
addEdge(1, 7, 11); addEdge(2, 3, 7); addEdge(2, 8, 2);
addEdge(2, 5, 4); addEdge(3, 4, 9); addEdge(3, 5, 14);
addEdge(4, 5, 10); addEdge(5, 6, 2); addEdge(6, 7, 1);
addEdge(6, 8, 6); addEdge(7, 8, 7);
int source = 0;
auto [dist, parent] = dijkstraWithPath(adj, source);
cout << "Shortest distances and paths from vertex " << source << ":" << endl;
for (int i = 0; i < n; i++) {
cout << " d[" << i << "] = " << dist[i] << " Path: ";
vector<int> path = reconstructPath(parent, i);
for (int j = 0; j < (int)path.size(); j++) {
cout << path[j];
if (j < (int)path.size() - 1) cout << " -> ";
}
cout << endl;
}
return 0;
}
C 实现
#include <stdio.h>
#include <limits.h>
#include <stdbool.h>
#define MAX_VERTICES 9
#define MAX_NEIGHBORS 6
#define MAX_HEAP_SIZE 200
#define MAX_PATH_LEN 10
typedef struct {
int vertex;
int weight;
} Edge;
typedef struct {
int dist;
int vertex;
} HeapNode;
typedef struct {
HeapNode data[MAX_HEAP_SIZE];
int size;
} MinHeap;
void heapPush(MinHeap* h, int dist, int vertex) {
int i = h->size++;
h->data[i].dist = dist;
h->data[i].vertex = vertex;
while (i > 0) {
int parent = (i - 1) / 2;
if (h->data[i].dist < h->data[parent].dist) {
HeapNode temp = h->data[i];
h->data[i] = h->data[parent];
h->data[parent] = temp;
i = parent;
} else break;
}
}
HeapNode heapPop(MinHeap* h) {
HeapNode top = h->data[0];
h->data[0] = h->data[--h->size];
int i = 0;
while (true) {
int left = 2 * i + 1, right = 2 * i + 2, smallest = i;
if (left < h->size && h->data[left].dist < h->data[smallest].dist) smallest = left;
if (right < h->size && h->data[right].dist < h->data[smallest].dist) smallest = right;
if (smallest != i) {
HeapNode temp = h->data[i];
h->data[i] = h->data[smallest];
h->data[smallest] = temp;
i = smallest;
} else break;
}
return top;
}
bool heapEmpty(MinHeap* h) { return h->size == 0; }
// Dijkstra with path reconstruction
void dijkstraWithPath(Edge adj[][MAX_NEIGHBORS], int adjCount[], int n,
int source, int dist[], int parent[]) {
MinHeap pq = {.size = 0};
for (int i = 0; i < n; i++) {
dist[i] = INT_MAX;
parent[i] = -1;
}
dist[source] = 0;
heapPush(&pq, 0, source);
while (!heapEmpty(&pq)) {
HeapNode top = heapPop(&pq);
if (top.dist > dist[top.vertex]) continue;
int u = top.vertex;
for (int j = 0; j < adjCount[u]; j++) {
int v = adj[u][j].vertex;
int w = adj[u][j].weight;
if (dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
parent[v] = u;
heapPush(&pq, dist[v], v);
}
}
}
}
// Reconstruct path from source to target
void reconstructPath(int parent[], int target, int path[], int* pathLen) {
*pathLen = 0;
for (int cur = target; cur != -1; cur = parent[cur]) {
path[(*pathLen)++] = cur;
}
// Reverse path in-place
for (int i = 0, j = *pathLen - 1; i < j; i++, j--) {
int temp = path[i];
path[i] = path[j];
path[j] = temp;
}
}
int main() {
Edge adj[MAX_VERTICES][MAX_NEIGHBORS];
int adjCount[MAX_VERTICES] = {0};
#define ADD_EDGE(u, v, w) do { \
adj[u][adjCount[u]].vertex = v; \
adj[u][adjCount[u]].weight = w; \
adjCount[u]++; \
adj[v][adjCount[v]].vertex = u; \
adj[v][adjCount[v]].weight = w; \
adjCount[v]++; \
} while(0)
ADD_EDGE(0, 1, 4); ADD_EDGE(0, 7, 8); ADD_EDGE(1, 2, 8);
ADD_EDGE(1, 7, 11); ADD_EDGE(2, 3, 7); ADD_EDGE(2, 8, 2);
ADD_EDGE(2, 5, 4); ADD_EDGE(3, 4, 9); ADD_EDGE(3, 5, 14);
ADD_EDGE(4, 5, 10); ADD_EDGE(5, 6, 2); ADD_EDGE(6, 7, 1);
ADD_EDGE(6, 8, 6); ADD_EDGE(7, 8, 7);
int dist[MAX_VERTICES], parent[MAX_VERTICES];
int source = 0;
dijkstraWithPath(adj, adjCount, MAX_VERTICES, source, dist, parent);
printf("Shortest distances and paths from vertex %d:\n", source);
for (int i = 0; i < MAX_VERTICES; i++) {
int path[MAX_PATH_LEN], pathLen;
reconstructPath(parent, i, path, &pathLen);
printf(" d[%d] = %2d Path: ", i, dist[i]);
for (int j = 0; j < pathLen; j++) {
printf("%d", path[j]);
if (j < pathLen - 1) printf(" -> ");
}
printf("\n");
}
return 0;
}
Python 实现
import heapq
def dijkstra_with_path(adj, source):
"""Dijkstra with path reconstruction using parent array."""
n = len(adj)
dist = [float('inf')] * n
parent = [-1] * n
dist[source] = 0
pq = [(0, source)]
while pq:
d, u = heapq.heappop(pq)
if d > dist[u]:
continue
for v, w in adj[u]:
if dist[u] + w < dist[v]:
dist[v] = dist[u] + w
parent[v] = u
heapq.heappush(pq, (dist[v], v))
return dist, parent
def reconstruct_path(parent, target):
"""Reconstruct path from source to target."""
path = []
cur = target
while cur != -1:
path.append(cur)
cur = parent[cur]
path.reverse()
return path
def main():
n = 9
adj = {i: [] for i in range(n)}
edges = [
(0, 1, 4), (0, 7, 8), (1, 2, 8), (1, 7, 11),
(2, 3, 7), (2, 8, 2), (2, 5, 4), (3, 4, 9),
(3, 5, 14), (4, 5, 10), (5, 6, 2), (6, 7, 1),
(6, 8, 6), (7, 8, 7),
]
for u, v, w in edges:
adj[u].append((v, w))
adj[v].append((u, w))
source = 0
dist, parent = dijkstra_with_path(adj, source)
print(f"Shortest distances and paths from vertex {source}:")
for i in range(n):
path = reconstruct_path(parent, i)
path_str = " -> ".join(map(str, path))
print(f" d[{i}] = {dist[i]:2d} Path: {path_str}")
if __name__ == "__main__":
main()
Go 实现
package main
import (
"container/heap"
"fmt"
"math"
)
type Edge struct {
To int
Weight int
}
type HeapNode struct {
Dist int
Vertex int
}
type MinHeap []HeapNode
func (h MinHeap) Len() int { return len(h) }
func (h MinHeap) Less(i, j int) bool { return h[i].Dist < h[j].Dist }
func (h MinHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *MinHeap) Push(x interface{}) { *h = append(*h, x.(HeapNode)) }
func (h *MinHeap) Pop() interface{} {
old := *h
n := len(old)
x := old[n-1]
*h = old[:n-1]
return x
}
// Dijkstra with path reconstruction using parent array
func dijkstraWithPath(adj [][]Edge, source int) ([]int, []int) {
n := len(adj)
dist := make([]int, n)
parent := make([]int, n)
for i := range dist {
dist[i] = math.MaxInt
parent[i] = -1
}
dist[source] = 0
pq := &MinHeap{}
heap.Init(pq)
heap.Push(pq, HeapNode{Dist: 0, Vertex: source})
for pq.Len() > 0 {
top := heap.Pop(pq).(HeapNode)
if top.Dist > dist[top.Vertex] {
continue
}
u := top.Vertex
for _, e := range adj[u] {
if dist[u]+e.Weight < dist[e.To] {
dist[e.To] = dist[u] + e.Weight
parent[e.To] = u
heap.Push(pq, HeapNode{Dist: dist[e.To], Vertex: e.To})
}
}
}
return dist, parent
}
// Reconstruct path from source to target using parent array
func reconstructPath(parent []int, target int) []int {
var path []int
for cur := target; cur != -1; cur = parent[cur] {
path = append(path, cur)
}
// Reverse to get source -> target order
for i, j := 0, len(path)-1; i < j; i, j = i+1, j-1 {
path[i], path[j] = path[j], path[i]
}
return path
}
func main() {
n := 9
adj := make([][]Edge, n)
addEdge := func(u, v, w int) {
adj[u] = append(adj[u], Edge{To: v, Weight: w})
adj[v] = append(adj[v], Edge{To: u, Weight: w})
}
addEdge(0, 1, 4)
addEdge(0, 7, 8)
addEdge(1, 2, 8)
addEdge(1, 7, 11)
addEdge(2, 3, 7)
addEdge(2, 8, 2)
addEdge(2, 5, 4)
addEdge(3, 4, 9)
addEdge(3, 5, 14)
addEdge(4, 5, 10)
addEdge(5, 6, 2)
addEdge(6, 7, 1)
addEdge(6, 8, 6)
addEdge(7, 8, 7)
source := 0
dist, parent := dijkstraWithPath(adj, source)
fmt.Printf("Shortest distances and paths from vertex %d:\n", source)
for i := 0; i < n; i++ {
path := reconstructPath(parent, i)
pathStr := ""
for j, v := range path {
if j > 0 {
pathStr += " -> "
}
pathStr += fmt.Sprintf("%d", v)
}
fmt.Printf(" d[%d] = %2d Path: %s\n", i, dist[i], pathStr)
}
}
上述代码在优先队列版本的 Dijkstra 基础上增加了 parent 数组。每当松弛操作成功更新 dist[v] 时,同时记录 parent[v] = u,表示在最短路径中 v 的前驱是 u。reconstructPath 函数从目标顶点出发,沿着 parent 链回溯到源点(parent 值为 -1),将路径反转后即得到从源点到目标的完整路径。例如,从顶点 0 到顶点 4 的最短路径为 0 -> 7 -> 6 -> 5 -> 4,距离为 8+1+2+10=21。
运行该程序将输出:
Shortest distances and paths from vertex 0:
d[0] = 0 Path: 0
d[1] = 4 Path: 0 -> 1
d[2] = 12 Path: 0 -> 1 -> 2
d[3] = 19 Path: 0 -> 1 -> 2 -> 3
d[4] = 21 Path: 0 -> 7 -> 6 -> 5 -> 4
d[5] = 11 Path: 0 -> 7 -> 6 -> 5
d[6] = 9 Path: 0 -> 7 -> 6
d[7] = 8 Path: 0 -> 7
d[8] = 14 Path: 0 -> 1 -> 2 -> 8
Dijkstra 算法的性质
下表总结了 Dijkstra 算法在不同实现方式下的时间和空间复杂度:
| 实现方式 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| 数组版本(Array) | O(V^2) | O(V) | 稠密图(Dense Graph),边数接近 V^2 |
| 优先队列版本(Priority Queue) | O((V+E)logV) | O(V+E) | 稀疏图(Sparse Graph),边数远小于 V^2 |
其中 V 是顶点数(Vertex Count),E 是边数(Edge Count)。
Dijkstra 算法的关键性质:
- 贪心正确性:Dijkstra 算法基于贪心策略,每次选择当前最近的未确定顶点。由于所有边权非负,一旦某个顶点被确定为最近,其距离就不会再被更新——这是算法正确性的核心保证。
- 非负权约束:算法要求所有边的权重为非负数。如果存在负权边,贪心选择可能不是最优的,应改用 Bellman-Ford 算法或 SPFA 算法。
- 单源最短路径:Dijkstra 算法解决的是单源最短路径问题(Single-Source Shortest Path),即从单一源点出发到所有其他顶点的最短距离。如果需要求所有顶点对之间的最短路径,可使用 Floyd-Warshall 算法。
- 最短路径树:算法生成的
parent数组构成了一棵以源点为根的最短路径树(Shortest Path Tree),树中从根到任意节点的路径就是原图中的最短路径。
Dijkstra 算法的典型应用场景:
| 应用场景 | 说明 |
|---|---|
| 导航系统(Navigation System) | 计算地图上两点之间的最短驾驶路线 |
| 网络路由(Network Routing) | OSPF 协议使用 Dijkstra 计算最短路由路径 |
| 社交网络分析 | 计算用户之间的最小社交距离 |
| 游戏开发 | 寻路系统(Pathfinding)中计算角色移动路径 |
| 物流调度 | 计算最优运输路线,最小化总运输成本 |
与其他最短路径算法的对比:
| 算法 | 时间复杂度 | 适用条件 | 特点 |
|---|---|---|---|
| Dijkstra(数组) | O(V^2) | 非负权 | 简单直观,适合稠密图 |
| Dijkstra(优先队列) | O((V+E)logV) | 非负权 | 高效,适合稀疏图 |
| Bellman-Ford | O(VE) | 允许负权 | 可检测负权环 |
| SPFA | 平均 O(E),最坏 O(VE) | 允许负权 | Bellman-Ford 的队列优化 |
| Floyd-Warshall | O(V^3) | 允许负权 | 求所有顶点对的最短路径 |

浙公网安备 33010602011771号