一.算法思想
初始化阶段
定义dis[i]表示起点k到节点i的最短路径长度
初始时dis[k]=0(起点到自身距离为0)
其他所有dis[i]=∞(表示尚未计算出最短路径)
集合划分
将图中节点分为两个集合:
已确定集S:最短路径已经暂时确定的节点
未确定集Q:尚未确定最短路径的节点
初始时S为空集,Q包含所有节点
迭代过程
while Q不为空时:
a. 从Q中选择dis值最小的节点u(即当前离起点最近的未确定节点)
b. 将u从Q移到S中(确认u的最短路径已找到)
c. 对u的所有邻居v进行松弛操作:
if dis[v] > dis[u] + w(u,v):
更新dis[v] = dis[u] + w(u,v)
正确性保证
贪心选择性质:每次选择dis最小的节点u,此时dis[u]就是最终最短距离
数学归纳法证明:
基础:起点k的距离0是正确的
假设:前n次选择的最短路径都正确
递推:第n+1次选择的节点u,其路径必然是最短的(反证法可证)
终止条件
关键特性
要求图中无负权边(否则贪心选择不成立)
时间复杂度:
普通实现:O(V²)
优先队列优化:O(E + VlogV)
二.代码
堆优化版本
O(E + VlogV)
适用于稀疏图,边数E小于V²
using namespace std;
typedef pair<int, int> pii; // (distance, node)
vector<int> dijkstra(vector<vector<pii>>& graph, int start) {
int n = graph.size();
vector<int> dist(n, INT_MAX);
priority_queue<pii, vector<pii>, greater<pii>> pq;
dist[start] = 0;
pq.push({0, start});
while (!pq.empty()) {
int u = pq.top().second;
int current_dist = pq.top().first;
pq.pop();
if (current_dist > dist[u]) continue;
for (auto& edge : graph[u]) {
int v = edge.first;
int weight = edge.second;
if (dist[v] > dist[u] + weight) {
dist[v] = dist[u] + weight;
pq.push({dist[v], v});
}
}
}
return dist;
}
为什么需要反复入队?
优先队列不支持动态更新
与其他最短路算法如SPFA的原理不一样,SPFA反复入队的原理是带反悔的贪心
当节点 v 的距离需要更新时(dis[v] = dis[u] + w(u,v)),无法直接修改队列中已有的 v 的旧距离值。
解决方案:直接将 v 和新距离插入队列(即使 v 已在队列中),队列中可能存在同一个节点 v 的多个副本(对应不同时刻的距离值)。
普通版本
O(V²)
void dijkstra(vector<vector<pair<int,int>>>& graph, int start) {
int V = graph.size();
vector<int> dist(V, INT_MAX);
queue<int> q;
dist[start] = 0;
q.push(start);
while (!q.empty()) {
int u = q.front();
q.pop();
for (auto& edge : graph[u]) {
int v = edge.first;
int weight = edge.second;
if (dist[u] + weight < dist[v]) {
dist[v] = dist[u] + weight;
q.push(v); // 重新入队以传播更新
}
}
}
}