一、Dijkstra 算法简介
Dijkstra(迪杰斯特拉)算法是一种单源最短路径算法,用于在带非负权重的图中,计算某个起点到其它所有顶点的最短距离。它由荷兰计算机科学家 Edsger W. Dijkstra 在 1956 年提出。
基本思想
Dijkstra 算法的核心思路是贪心策略:
每次从当前未访问的节点中选择距离起点最近的一个,确定它的最短路径,然后用它去更新其它节点的最短距离,直到所有节点都确定最短路径。
工作过程
假设:
- 图有 VVV 个顶点
- 用
dist[]
数组记录起点到各顶点的最短距离 - 用
visited[]
记录哪些顶点已经确定了最短路径
步骤:
-
初始化:
dist[start] = 0
- 其他
dist[v] = ∞
visited[]
全部为false
-
从未访问的顶点中,选取 dist 最小的节点 u,标记
visited[u] = true
-
遍历 u 的所有邻居 v:
- 如果
dist[v] > dist[u] + w(u,v)
,则更新dist[v]
- 如果
-
重复步骤 2~3,直到所有节点都被访问,或
dist
数组不再变化
伪代码
void dijkstra(int start, int n, vector<vector<pair<int,int>>>& graph) {
vector<int> dist(n, INT_MAX);
vector<bool> visited(n, false);
dist[start] = 0;
for (int i = 0; i < n; i++) {
int u = -1, minDist = INT_MAX;
for (int j = 0; j < n; j++) {
if (!visited[j] && dist[j] < minDist) {
u = j;
minDist = dist[j];
}
}
if (u == -1) break; // 剩下的不可达
visited[u] = true;
for (auto [v, w] : graph[u]) {
if (!visited[v] && dist[v] > dist[u] + w) {
dist[v] = dist[u] + w;
}
}
}
// 输出结果
for (int i = 0; i < n; i++)
cout << "Start -> " << i << " : " << dist[i] << endl;
}
如果用 优先队列(堆)优化,复杂度可以从 O(V2)O(V^2)O(V2) 降到 O((V+E)logV)O((V+E)\log V)O((V+E)logV)。
二、C++ 实现示例
下面的示例代码演示了如何在 C++ 中使用优先队列(priority_queue)来实现单源最短路径的 Dijkstra 算法,并在 main
函数中进行简单测试。我们采用邻接表存储图,顶点编号默认从 0 开始。
#include <iostream>
#include <vector>
#include <queue>
#include <limits>
using namespace std;
// 使用结构体存储图中每条边的信息 (邻接表的节点),
// 其中邻接节点是 v,边权重是 w
struct Edge {
int v;
int w;
Edge(int _v, int _w) : v(_v), w(_w) {}
};
// 输入:
// graph - 邻接表, graph[u] 表示从 u 出发的所有边
// source - 源点
// 输出:
// 返回 dist 数组,表示从 source 到每个顶点的最短距离
vector<int> dijkstra(const vector<vector<Edge>>& graph, int source) {
// 顶点数
int V = graph.size();
// 初始化距离数组,都设为 "无限大"
vector<int> dist(V, numeric_limits<int>::max());
dist[source] = 0; // 源点距离置为 0
// 小根堆(最小优先队列),
// 队列中每个元素存储的是 pair<当前距离, 当前顶点>
// 这样可以根据最小距离进行弹出
priority_queue< pair<int,int>, vector<pair<int,int>>, greater<pair<int,int>> > pq;
// 先把源点入队
pq.push({0, source});
// 当队列不为空时,反复取出距离最小的顶点进行松弛
while(!pq.empty()) {
// 取出队首: 距离最小的顶点
auto [currDist, u] = pq.top();
pq.pop();
// 如果队首的距离比 dist[u] 更大,说明已被更新过,跳过
if(currDist > dist[u]) {
continue;
}
// 松弛操作
for(auto &edge : graph[u]) {
int v = edge.v;
int w = edge.w;
// 如果 u->v 可以更新 dist[v]
if(dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
pq.push({dist[v], v});
}
}
}
return dist;
}
// 测试
int main() {
// 假设我们有一个包含 5 个顶点(0~4) 的有向图:
// graph[u] 是从顶点 u 出发的所有带权边
// 例如:edge(0->1, 权重10) 表示从0到1的边权重为10
vector<vector<Edge>> graph = {
{ {1,10}, {4,3} }, // 0 -> 1 (10), 0 -> 4 (3)
{ {2,2}, {4,4} }, // 1 -> 2 (2), 1 -> 4 (4)
{ {3,9} }, // 2 -> 3 (9)
{ {2,7} }, // 3 -> 2 (7)
{ {1,1}, {2,8} } // 4 -> 1 (1), 4 -> 2 (8)
};
int source = 0; // 以0号顶点作为源点
vector<int> dist = dijkstra(graph, source);
// 输出结果
cout << "从源点 " << source << " 到各顶点的最短距离:" << endl;
for(int i = 0; i < (int)dist.size(); i++) {
if(dist[i] == numeric_limits<int>::max()) {
cout << "顶点 " << i << ": 无法到达" << endl;
} else {
cout << "顶点 " << i << ": " << dist[i] << endl;
}
}
return 0;
}
运行示例
代码说明
-
邻接表的存储方式
vector<vector<Edge>> graph;
其中graph[u]
存放从顶点u
出发的所有边(Edge
),Edge
结构体包含目标顶点v
和边权重w
。 -
Dijkstra 函数
- 使用
dist[v]
数组来存储从源点到顶点v
的最短距离,初始都设为“无限大”,源点距离设为 0。 - 使用一个小根堆(最小优先队列)来获取当前距离最小的未确定顶点。
- 每弹出一个顶点
u
,就尝试松弛u
对应的邻接边,并更新相邻顶点的距离。 - 松弛(relaxation):
if (dist[u] + w < dist[v]) { dist[v] = dist[u] + w; // 将更新后的距离重新压入优先队列 pq.push({dist[v], v}); }
- 使用
-
在
main
函数中测试- 我们构造了一个简单的5个顶点的有向图,并在源点为
0
的情况下运行算法。 - 将计算得到的最短路径距离输出到终端。
- 我们构造了一个简单的5个顶点的有向图,并在源点为
三、应用场景
地图导航
GPS、百度地图、高德地图等会用 Dijkstra 算法(或其优化版 A*)计算最短路径
网络路由
OSPF(开放式最短路径优先协议)就是基于 Dijkstra 算法构建路由表
游戏开发
NPC 移动寻路、AI 决策(与 A* 算法结合)
物流配送优化
计算送货路径、运输网络最短时间
通信网络优化
选择最小延迟的传输路径
机器人路径规划
移动机器人、AGV(自动导引车)寻路
四、注意事项
权重必须非负:如果存在负权边,应使用 Bellman-Ford 或 SPFA
适合稠密图:稀疏图建议用优先队列优化
单源最短路径:如果需要多源,通常要多次运行或使用 Floyd-Warshall