Dijkstra是单源最短路径问题的经典算法,由荷兰计算机科学家Edsger W. Dijkstra于1956年提出。它适用于带非负权重的有向图或无向图,能够高效计算从起点到所有其他节点的最短路径。本文将全面解析 Dijkstra
1. 核心思想
Dijkstra算法采用贪心策略,逐步确定从起点到其他节点的最短路径,其核心步骤如下:
-
初始化:
- 维护一个距离数组
dist[],'dist[u]dist[u]表示起点s到节点 'uu的最短距离,初始时 'dist[s] = 0dist[s] = 0,其余为∞。 - 使用优先队列(最小堆)存储待处理的节点,按
dist值排序。
- 维护一个距离数组
-
松弛作(Relaxation):
- 对于当前节点 'u
u,遍历其所有邻居v,检查是否可以通过 'uu缩短s到 'vv的距离:
- 对于当前节点 'u
-
终止条件:
- 当优先队列为空时,算法结束,此时 'dist[]
dist[]存储了起点到所有节点的最短距离。
- 当优先队列为空时,算法结束,此时 'dist[]
2. 算法特性
|
指标
|
说明
|
|---|---|
|
时间复杂度
|
O((V+E) log V)(优先队列优化)<br<br>O(V²)(朴素实现,适用于稠密图)
|
|
空间复杂度
|
O(V)(存储
dist数组和优先队列) |
|
适用场景
|
非负权重图的单源最短路径(如路由规划、地图导航)
|
|
限制
|
不能处理负权边(需改用 Bellman-Ford
|
3.python实现
import heapq
def dijkstra(graph, start):
"""
Dijkstra算法实现,计算从start节点到所有其他节点的最短路径
参数:
graph: 邻接表表示的图,格式为 {节点: {邻居: 边权重}}
start: 起始节点
返回:
distances: 字典,存储从start到各节点的最短距离
predecessors: 字典,存储最短路径中的前驱节点,用于路径重建
"""
# 初始化距离字典,初始值为无穷大
distances = {node: float('inf') for node in graph}
distances[start] = 0 # 起点到自身的距离为0
# 初始化前驱字典,用于路径重建
predecessors = {node: None for node in graph}
# 优先队列(最小堆),存储 (距离, 节点) 元组
priority_queue = [(0, start)]
while priority_queue:
# 弹出当前距离最小的节点
current_distance, current_node = heapq.heappop(priority_queue)
# 如果当前距离大于已记录的最短距离,跳过
if current_distance > distances[current_node]:
continue
# 遍历当前节点的所有邻居
for neighbor, weight in graph[current_node].items():
distance = current_distance + weight
# 如果通过当前节点到达邻居的距离更短,则更新距离和前驱
if distance < distances[neighbor]:
distances[neighbor] = distance
predecessors[neighbor] = current_node
# 将新的距离加入优先队列
heapq.heappush(priority_queue, (distance, neighbor))
return distances, predecessors
4.C++实现
#include <iostream>
#include <vector>
#include <queue>
#include <unordered_map>
#include <limits>
using namespace std;
// 定义图的结构:邻接表,格式为 {节点: {邻居节点, 边权重}}
using Graph = unordered_map<int, vector<pair<int, int>>>;
// Dijkstra算法实现
pair<unordered_map<int, int>, unordered_map<int, int>> dijkstra(const Graph& graph, int start) {
// 初始化距离字典,初始值为无穷大
unordered_map<int, int> distances;
for (const auto& node : graph) {
distances[node.first] = numeric_limits<int>::max();
}
distances[start] = 0; // 起点到自身的距离为0
// 初始化前驱字典,用于路径重建
unordered_map<int, int> predecessors;
for (const auto& node : graph) {
predecessors[node.first] = -1; // -1表示无前驱
}
// 优先队列(最小堆),存储 (距离, 节点) 对
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
pq.push({0, start});
while (!pq.empty()) {
// 弹出当前距离最小的节点
int current_distance = pq.top().first;
int current_node = pq.top().second;
pq.pop();
// 如果当前距离大于已记录的最短距离,跳过
if (current_distance > distances[current_node]) {
continue;
}
// 遍历当前节点的所有邻居
if (graph.find(current_node) != graph.end()) {
for (const auto& neighbor : graph.at(current_node)) {
int neighbor_node = neighbor.first;
int weight = neighbor.second;
int distance = current_distance + weight;
// 如果通过当前节点到达邻居的距离更短,则更新距离和前驱
if (distance < distances[neighbor_node]) {
distances[neighbor_node] = distance;
predecessors[neighbor_node] = current_node;
pq.push({distance, neighbor_node});
}
}
}
}
return {distances, predecessors};
}
5.时间复杂度分析
1. 朴素实现(邻接矩阵 + 数组)
数据结构:
- 邻接矩阵存储图(空间复杂度 \(O(V^2)\))
- 数组存储各节点的最短距离
dist[] - 集合标记已确定最短路径的节点
算法流程:
- 初始化:
- 主循环:重复 V 次
- 从未确定节点中选择最小
dist的节点 → \(O(V)\) - 遍历该节点的所有邻居(检查邻接矩阵的一行)→ \(O(V)\)
- 更新邻居的距离 → \(O(1)\)
- 从未确定节点中选择最小
总时间复杂度: O(V^2)
适用场景:
- 稠密图(边数 \(E \approx V^2\))
- 节点数 V 较小(如 \(V < 1000\))
2. 优先队列优化(邻接表 + 最小堆)
数据结构:
- 邻接表存储图(空间复杂度 (O(V + E)))
- 最小堆(优先队列)维护未确定节点的
(距离, 节点)对
算法流程:
- 初始化:
- 主循环:
- 每次从堆中取出最小
dist的节点 → (O(\log V))(堆操作) - 遍历该节点的所有邻居(邻接表)→ (O(E_{out}))
- 对每个邻居:更新距离并将其插入堆(或更新堆中节点)→ (O(log V))
- 每次从堆中取出最小
总时间复杂度:
- 每个节点出堆一次 → \(O(V \log V)\)
- 每条边被遍历一次,每次可能触发堆操作 → \(O(E \log V)\) \(O((V + E) \log V)\)
适用场景:
- 稀疏图(边数 \(E \ll V^2\))
- 节点数 V 较大(如 \(V > 1000\))
对比总结
| 实现方式 | 时间复杂度 | 适用场景 |
|---|---|---|
| 朴素实现(邻接矩阵) | \(O(V^2)\) | 稠密图(\(E \approx V^2\)) |
| 优先队列(邻接表) | \(O((V + E) \log V)\) | 稀疏图(\(E \ll V^2\)) |
6.总结
算法特性
| 指标 | 说明 |
|---|---|
| 适用场景 | 非负权重图的单源最短路径问题(如地图导航、网络路由) |
| 时间复杂度 | - 朴素实现(邻接矩阵):\(O(V^2)\) - 优先队列优化(邻接表 + 最小堆):\(O((V + E) \log V)\) |
| 空间复杂度 | \(O(V)\)(存储距离数组和优先队列) |
| 局限性 | 无法处理负权边(需改用 Bellman-Ford 算法) |
实现方式对比
-
朴素实现:
- 数据结构:邻接矩阵、距离数组、标记集合。
- 时间复杂度:\(O(V^2)\),适用于稠密图(\(E \approx V^2\))或小规模图(\(V < 1000\))。
-
优先队列优化:
- 数据结构:邻接表、最小堆(优先队列)。
- 时间复杂度:\(O((V + E) \log V)\),适用于稀疏图(\(E \ll V^2\))或大规模图(\(V > 1000\))。
代码实现
- Python:使用
heapq模块实现最小堆,通过字典存储距离和前驱节点。 - C++:利用
priority_queue(最小堆)和unordered_map,结合邻接表实现算法。
总结
Dijkstra 算法以贪心策略为核心,通过优先队列优化显著提升了稀疏图的计算效率。其简洁的逻辑和广泛的适用性使其成为图论中解决单源最短路径问题的首选方案,但需注意其对负权边的限制。实际应用中,应根据图的密度和规模选择合适的实现方式,以平衡时间和空间复杂度。

2233

被折叠的 条评论
为什么被折叠?



