最短路径算法讲解(以 Dijkstra 为主)
一、引言:为什么需要最短路径?
在现实生活中,我们常常遇到这样的问题:
- 地图导航软件如何计算从A地到B地的最短路线?
- 网络通信中,数据包如何选择最优路径传输?
- 游戏中角色如何自动寻路?
这些问题都可以抽象为图论中的最短路径问题。
本讲义将围绕Dijkstra算法展开,适合已经掌握**深度优先搜索(DFS)和动态规划(DP)**基础的同学。我们将从图的基本概念讲起,逐步过渡到 Dijkstra 的原理、实现与优化,并提供配套 C++ 示例代码与练习题。
二、图的基本概念回顾
1. 图的定义
图由顶点(节点)和边组成:
- 无向图:边没有方向
- 有向图:边有方向
- 带权图:每条边有权值(如距离、时间等)
2. 图的表示方法
- 邻接矩阵:适用于稠密图
- 邻接表:适用于稀疏图,常用方式
// 邻接表示例(C++)
#include <vector>
#include <utility> // for pair
using namespace std;
const int MAXN = 100010;
vector<pair<int, int>> graph[MAXN]; // 存储 (终点, 权重)
三、最短路径问题分类
| 类型 | 描述 | 常用算法 |
|---|---|---|
| 单源最短路径 | 给定一个起点,求它到所有其他点的最短路径 | Dijkstra、Bellman-Ford |
| 多源最短路径 | 求任意两点之间的最短路径 | Floyd-Warshall |
本讲义重点讲解单源最短路径问题中的经典算法——Dijkstra算法。
四、Dijkstra 算法详解
1. 适用范围
- 图中不能有负权边
- 可用于有向图或无向图
- 权值必须非负
2. 核心思想
Dijkstra 是一种贪心策略 + 广度优先搜索的变体:
- 维护一个已确定最短路径的集合 S
- 每次从未确定的点中选择离起点最近的点 u 加入 S
- 用 u 更新其邻居的最短路径估计值
五、Dijkstra 算法实现(C++)
1. 使用堆优化的版本(推荐)
使用 priority_queue 实现最小堆,提升效率。
#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
using namespace std;
typedef pair<int, int> pii;
const int MAXN = 100010;
const int INF = 0x3f3f3f3f;
vector<pii> graph[MAXN]; // graph[u] 存储的是 (v, w)
int dist[MAXN];
bool visited[MAXN];
int n, m; // n个节点,m条边
void dijkstra(int start) {
memset(dist, INF, sizeof(dist));
dist[start] = 0;
priority_queue<pii, vector<pii>, greater<pii>> pq;
pq.push({0, start});
while (!pq.empty()) {
int u = pq.top().second;
int d = pq.top().first;
pq.pop();
if (d > dist[u]) continue;
for (auto edge : graph[u]) {
int v = edge.first;
int w = edge.second;
if (dist[v] > dist[u] + w) {
dist[v] = dist[u] + w;
pq.push({dist[v], v});
}
}
}
}
2. 示例图
int main() {
cin >> n >> m;
for (int i = 0; i < m; ++i) {
int u, v, w;
cin >> u >> v >> w;
graph[u].push_back({v, w});
// 若是无向图,再加一句:
// graph[v].push_back({u, w});
}
dijkstra(1); // 假设起点是节点1
for (int i = 1; i <= n; ++i) {
cout << "Distance from 1 to " << i << ": ";
if (dist[i] == INF)
cout << "INF" << endl;
else
cout << dist[i] << endl;
}
return 0;
}
六、进阶内容
1. 打印路径
我们可以记录前驱节点来还原路径:
int prev[MAXN]; // 记录前驱节点
void dijkstra_with_path(int start) {
memset(dist, INF, sizeof(dist));
memset(prev, -1, sizeof(prev));
dist[start] = 0;
priority_queue<pii, vector<pii>, greater<pii>> pq;
pq.push({0, start});
while (!pq.empty()) {
int u = pq.top().second;
int d = pq.top().first;
pq.pop();
if (d > dist[u]) continue;
for (auto edge : graph[u]) {
int v = edge.first;
int w = edge.second;
if (dist[v] > dist[u] + w) {
dist[v] = dist[u] + w;
prev[v] = u;
pq.push({dist[v], v});
}
}
}
}
void print_path(int end) {
if (prev[end] == -1 && end != 1) {
cout << "No path!" << endl;
return;
}
vector<int> path;
for (int v = end; v != -1; v = prev[v])
path.push_back(v);
reverse(path.begin(), path.end());
for (int node : path)
cout << node << " ";
cout << endl;
}