Dijkstra
算法原理:
Dijkstra算法是一种经典的用于计算单源最短路径的算法。它可以在带权重的图中找到从源节点到所有其他节点的最短路径。Dijkstra算法通过贪心策略,不断选择当前已知最短路径最小的节点,更新其邻接节点的路径长度,直到处理完所有节点。
适用场景:
- 图中的边权重为非负值。
- 需要计算从单一源点到其他所有节点的最短路径。
算法步骤:
- 初始化:设定源节点的距离为0,其他所有节点的距离为无穷大。将所有节点标记为未访问状态。
- 从未访问节点中选择距离最小的节点作为当前节点。
- 对于当前节点的每一个未访问的邻接节点,计算从源节点经过当前节点到达该邻接节点的距离。如果该距离小于之前记录的距离,则更新该距离。
- 标记当前节点为已访问状态。
- 重复步骤2-4,直到所有节点都被访问过。
实现步骤:
- 使用一个数组或字典记录每个节点的当前最短路径。
- 使用一个优先队列(最小堆)加速获取当前最短路径最小的节点。
- 不断从优先队列中取出最短路径最小的节点,更新其邻接节点的路径长度。
- 直到优先队列为空,所有节点的最短路径均已确定。
伪代码:
function Dijkstra(Graph, source):
dist[source] = 0
create priority queue Q
for each vertex v in Graph:
if v ≠ source:
dist[v] = infinity
prev[v] = undefined
Q.add_with_priority(v, dist[v])
while Q is not empty:
u = Q.extract_min()
for each neighbor v of u:
alt = dist[u] + length(u, v)
if alt < dist[v]:
dist[v] = alt
prev[v] = u
Q.decrease_priority(v, alt)
return dist, prev
C语言代码:
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#define V 9 // 图中的顶点数
// 寻找当前未访问节点中距离最小的节点
int minDistance(int dist[], int sptSet[]) {
int min = INT_MAX, min_index;
for (int v = 0; v < V; v++) {
if (sptSet[v] == 0 && dist[v] <= min) {
min = dist[v], min_index = v;
}
}
return min_index;
}
// 打印从源节点到所有其他节点的最短路径
void printSolution(int dist[]) {
printf("Vertex \t Distance from Source\n");
for (int i = 0; i < V; i++) {
printf("%d \t\t %d\n", i, dist[i]);
}
}
// 实现Dijkstra算法
void dijkstra(int graph[V][V], int src) {
int dist[V]; // 存储源节点到其他节点的最短距离
int sptSet[V]; // 标记节点是否已被处理
// 初始化所有节点的最短距离为无穷大,sptSet为未访问状态
for (int i = 0; i < V; i++) {
dist[i] = INT_MAX, sptSet[i] = 0;
}
dist[src] = 0;
// 遍历所有节点
for (int count = 0; count < V - 1; count++) {
int u = minDistance(dist, sptSet);
sptSet[u] = 1;
// 更新节点的最短路径
for (int v = 0; v < V; v++) {
if (!sptSet[v] && graph[u][v] && dist[u] != INT_MAX && dist[u] + graph[u][v] < dist[v]) {
dist[v] = dist[u] + graph[u][v];
}
}
}
printSolution(dist);
}
int main() {
int graph[V][V] = {{0, 4, 0, 0, 0, 0, 0, 8, 0},
{4, 0, 8, 0, 0, 0, 0, 11, 0},
{0, 8, 0, 7, 0, 4, 0, 0, 2},
{0, 0, 7, 0, 9, 14, 0, 0, 0},
{0, 0, 0, 9, 0, 10, 0, 0, 0},
{0, 0, 4, 14, 10, 0, 2, 0, 0},
{0, 0, 0, 0, 0, 2, 0, 1, 6},
{8, 11, 0, 0, 0, 0, 1, 0, 7},
{0, 0, 2, 0, 0, 0, 6, 7, 0}};
dijkstra(graph, 0);
return 0;
}
代码解释:
int minDistance(int dist[], int sptSet[])
:找到未访问节点中距离最小的节点。void printSolution(int dist[])
:打印从源节点到所有其他节点的最短路径。void dijkstra(int graph[V][V], int src)
:实现Dijkstra算法,从源节点计算到所有其他节点的最短路径。int main()
:主函数,定义图的邻接矩阵并调用Dijkstra算法计算最短路径。
算法特点:
- 时间复杂度:O(V^2)(使用数组实现优先队列时),使用二叉堆可以优化到O((V + E) log V)。
- 适用于图中的边权重为非负值的情况。
- 通过贪心策略逐步找到从源节点到其他节点的最短路径。
通过上述代码和解释,读者应该可以理解Dijkstra算法的原理和实现步骤,并在实际应用中使用该算法计算最短路径。对于考研初试,只需掌握手算方法,我们以东南大学2000年真题为例进行讲解:
初始状态:
节点 | 距离 | 前驱 |
---|---|---|
a | 0 | - |
b | ∞ | - |
c | ∞ | - |
d | ∞ | - |
e | ∞ | - |
f | ∞ | - |
g | ∞ | - |
Round 1:
选择距离最小的节点a,更新其邻接节点的距离。
- 更新节点c的距离为2(0 + 2),前驱为a。
- 更新节点d的距离为12(0 + 12),前驱为a。
- 更新节点b的距离为15(0 + 15),前驱为a。
节点 | 距离 | 前驱 |
---|---|---|
a | 0 | - |
b | 15 | a |
c | 2 | a |
d | 12 | a |
e | ∞ | - |
f | ∞ | - |
g | ∞ | - |
Round 2:
选择距离最小的节点c,更新其邻接节点的距离。
- 更新节点e的距离为10(2 + 8),前驱为c。
- 更新节点f的距离为6(2 + 4),前驱为c。
节点 | 距离 | 前驱 |
---|---|---|
a | 0 | - |
b | 15 | a |
c | 2 | a |
d | 12 | a |
e | 10 | c |
f | 6 | c |
g | ∞ | - |
Round 3:
选择距离最小的节点f,更新其邻接节点的距离。
- 更新节点d的距离为11(6 + 5),前驱为f。
- 更新节点g的距离为16(6 + 10),前驱为f。
节点 | 距离 | 前驱 |
---|---|---|
a | 0 | - |
b | 15 | a |
c | 2 | a |
d | 11 | f |
e | 10 | c |
f | 6 | c |
g | 16 | f |
Round 4:
选择距离最小的节点e,更新其邻接节点的距离。
- 节点e没有需要更新的邻接节点。
节点 | 距离 | 前驱 |
---|---|---|
a | 0 | - |
b | 15 | a |
c | 2 | a |
d | 11 | f |
e | 10 | c |
f | 6 | c |
g | 16 | f |
Round 5:
选择距离最小的节点d,更新其邻接节点的距离。
- 更新节点g的距离为13(11 + 2),前驱为d。
节点 | 距离 | 前驱 |
---|---|---|
a | 0 | - |
b | 15 | a |
c | 2 | a |
d | 11 | f |
e | 10 | c |
f | 6 | c |
g | 13 | d |
Round 6:
选择距离最小的节点g,更新其邻接节点的距离。
- 节点g没有需要更新的邻接节点。
节点 | 距离 | 前驱 |
---|---|---|
a | 0 | - |
b | 15 | a |
c | 2 | a |
d | 11 | f |
e | 10 | c |
f | 6 | c |
g | 13 | d |
Round 7:
选择距离最小的节点b,更新其邻接节点的距离。
- 节点b没有需要更新的邻接节点。
节点 | 距离 | 前驱 |
---|---|---|
a | 0 | - |
b | 15 | a |
c | 2 | a |
d | 11 | f |
e | 10 | c |
f | 6 | c |
g | 13 | d |
最终结果如下:
最短路径结果:
- a -> a: 0
- a -> b: 15 (路径:a-b)
- a -> c: 2 (路径:a-c)
- a -> d: 11 (路径:a-c-f-d)
- a -> e: 10 (路径:a-c-e)
- a -> f: 6 (路径:a-c-f)
- a -> g: 13 (路径:a-c-f-d-g)