
哈喽大家好!前面我们学完了图的存储、遍历、拓扑排序,今天解锁图论最核心、考试面试必考的两大高阶难点:
✅ 最短路径问题:解决"怎么走路程最短、成本最低"
✅ 关键路径问题:解决"工程最快多久完工、哪些步骤不能拖延"
这两个知识点是数据结构图论的收尾重点,也是考研、期末、算法面试的高频考点!本文全程通俗讲解,避开晦涩公式,搭配完整C++实战代码,看完直接吃透、直接复用。
一、先搞懂:两个核心问题的应用场景
1. 最短路径:最优路线问题
生活中随处可见:地图导航找最短路线、快递配送最优路径、网络数据传输最小时延。
核心定义:在带权图中,找到两个顶点之间权值和最小的路径。
分为两类:
• 单源最短路径:一个起点,到所有终点的最短路径(Dijkstra算法)
• 多源最短路径:任意两点之间的最短路径(Floyd算法)
2. 关键路径:工程进度问题
多用于项目管理、工程施工、任务调度。核心是基于**AOE网(带权有向无环图)**求解。
作用:计算整个工程的最短完工时间 ,找出不可拖延的关键任务,只要关键任务延误,整体工程必然延期。
二、最短路径:Dijkstra 算法(单源最短路径)
1. 算法核心原理
适用场景 :带权有向/无向图,权值非负,求单个起点到其余所有顶点的最短路径。
核心思想:贪心算法
每次从未确定最短路径的节点中,选出距离起点最近的节点,确认其最短距离,再更新其他节点的距离,层层松弛迭代,直到所有节点遍历完成。
2. 算法执行步骤
-
初始化:设置起点距离为0,其余节点距离为无穷大;
-
选取:找到当前距离起点最近、未访问过的节点;
-
松弛:通过该节点中转,更新相邻节点的最短距离;
-
循环迭代,直到所有节点最短路径确定。
3. 完整C++实战代码(邻接矩阵版,简单易懂)
cpp
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int INF = 0x3f3f3f3f; // 无穷大
const int MAXN = 105;
int graph[MAXN][MAXN]; // 邻接矩阵存图
int dist[MAXN]; // 存储起点到各点最短距离
bool vis[MAXN]; // 标记节点是否已确定最短路径
int n, m; // n个顶点,m条边
// Dijkstra算法:start为起点
void Dijkstra(int start) {
// 初始化
memset(vis, false, sizeof(vis));
for (int i = 1; i <= n; i++) {
dist[i] = graph[start][i];
}
vis[start] = true;
// 迭代n-1次,确定剩余n-1个节点
for (int i = 1; i < n; i++) {
// 找未访问的距离最小的节点
int minDist = INF, u = -1;
for (int j = 1; j <= n; j++) {
if (!vis[j] && dist[j] < minDist) {
minDist = dist[j];
u = j;
}
}
if (u == -1) break;
vis[u] = true;
// 松弛操作:更新相邻节点距离
for (int v = 1; v <= n; v++) {
if (!vis[v] && graph[u][v] != INF) {
dist[v] = min(dist[v], dist[u] + graph[u][v]);
}
}
}
}
int main() {
// 初始化图
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
graph[i][j] = (i == j) ? 0 : INF;
}
}
// 输入边
for (int i = 0; i < m; i++) {
int u, v, w;
cin >> u >> v >> w;
graph[u][v] = w;
}
// 求1号节点到所有节点的最短路径
Dijkstra(1);
// 输出结果
for (int i = 1; i <= n; i++) {
if (dist[i] == INF) cout << "1到" << i << ":不可达" << endl;
else cout << "1到" << i << "最短距离:" << dist[i] << endl;
}
return 0;
}
三、最短路径:Floyd 算法(多源最短路径)
1. 算法核心原理
适用场景 :可以处理负权边(不能处理负权回路),直接求解图中任意两点的最短路径。
核心思想:动态规划
枚举所有中转节点k,判断「i直接到j」和「i先到k再到j」哪个路径更短,不断迭代更新最短距离。
优点:代码极简、无需复杂逻辑;缺点:时间复杂度高O(n³),适合小规模图。
2. 完整C++实战代码
cpp
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int INF = 0x3f3f3f3f;
const int MAXN = 105;
int dist[MAXN][MAXN]; // 存储任意两点最短路径
int n, m;
void Floyd() {
// k为中转节点
for (int k = 1; k <= n; k++) {
// i为起点,j为终点
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
// 松弛更新
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
}
}
}
}
int main() {
cin >> n >> m;
// 初始化
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
dist[i][j] = (i == j) ? 0 : INF;
}
}
// 输入边
for (int i = 0; i < m; i++) {
int u, v, w;
cin >> u >> v >> w;
dist[u][v] = w;
}
Floyd();
// 输出任意两点最短路径
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (dist[i][j] == INF) cout << "∞ ";
else cout << dist[i][j] << " ";
}
cout << endl;
}
return 0;
}
四、关键路径(AOE网)核心详解
1. 基础概念区分
很多同学容易混淆 AOV 网和 AOE 网:
• AOV网:顶点表示活动,边表示优先级(用于拓扑排序)
• AOE网 :边表示活动(带权),顶点表示事件(用于关键路径)
AOE网是有向无环带权图,唯一源点(入度0)、唯一汇点(出度0)。
2. 四大核心参数(必考)
-
ve[i]:事件i的最早发生时间(从源点到i的最长路径)
-
vl[i]:事件i的最晚发生时间(不推迟工期的最晚时间)
-
e[k]:活动k的最早开始时间
-
l[k]:活动k的最晚开始时间
关键活动判定 :e[k] == l[k],所有关键活动组成的路径即为关键路径。
核心结论:关键路径可能有多条,所有关键路径都走完,工程才算完工。
3. 求解步骤
-
拓扑排序得到顶点序列;
-
正向遍历拓扑序列,求所有事件的最早时间 ve;
-
逆向遍历拓扑序列,求所有事件的最晚时间 vl;
-
计算所有活动的 e、l,筛选 e=l 的关键活动,拼接关键路径。
4. C++关键路径完整实操代码
cpp
#include <iostream>
#include <queue>
#include <vector>
#include <cstring>
using namespace std;
const int MAXN = 105;
const int INF = 0x3f3f3f3f;
// 边结构体:活动
struct Edge {
int to, w;
Edge(int t, int ww) : to(t), w(ww) {}
};
vector<Edge> graph[MAXN];
int inDegree[MAXN]; // 入度
int ve[MAXN], vl[MAXN];// 事件最早、最晚时间
int n, m;
vector<int> topoSeq; // 拓扑序列
// 拓扑排序
bool TopoSort() {
queue<int> q;
for (int i = 1; i <= n; i++) {
if (inDegree[i] == 0) q.push(i);
}
while (!q.empty()) {
int u = q.front();
q.pop();
topoSeq.push_back(u);
for (Edge e : graph[u]) {
int v = e.to;
inDegree[v]--;
if (inDegree[v] == 0) q.push(v);
}
}
return topoSeq.size() == n;
}
// 关键路径求解
void CriticalPath() {
// 1. 求事件最早发生时间 ve
memset(ve, 0, sizeof(ve));
for (int u : topoSeq) {
for (Edge e : graph[u]) {
int v = e.to, w = e.w;
if (ve[v] < ve[u] + w) {
ve[v] = ve[u] + w;
}
}
}
// 2. 求事件最晚发生时间 vl
fill(vl, vl + n + 1, ve[topoSeq.back()]);
for (int i = topoSeq.size() - 1; i >= 0; i--) {
int u = topoSeq[i];
for (Edge e : graph[u]) {
int v = e.to, w = e.w;
if (vl[u] > vl[v] - w) {
vl[u] = vl[v] - w;
}
}
}
// 3. 遍历所有边,筛选关键活动
cout << "===== 关键活动列表 =====" << endl;
for (int u = 1; u <= n; u++) {
for (Edge e : graph[u]) {
int v = e.to, w = e.w;
int e_start = ve[u]; // 活动最早开始
int l_start = vl[v] - w; // 活动最晚开始
if (e_start == l_start) {
cout << "关键活动:" << u << " -> " << v << ",耗时:" << w << endl;
}
}
}
cout << "工程最短完工总时间:" << ve[topoSeq.back()] << endl;
}
int main() {
cin >> n >> m;
memset(inDegree, 0, sizeof(inDegree));
for (int i = 0; i < m; i++) {
int u, v, w;
cin >> u >> v >> w;
graph[u].emplace_back(v, w);
inDegree[v]++;
}
if (TopoSort()) {
CriticalPath();
} else {
cout << "图存在环,无法求解关键路径!" << endl;
}
return 0;
}
五、核心考点总结(期末/考研必背)
1. 最短路径算法对比
✅ Dijkstra:单源、非负权、贪心、效率高,适合大规模图
✅ Floyd:多源、支持负权(无负环)、代码简单、适合小规模图
2. 关键路径核心结论
-
关键路径长度 = 整个工程的最短完工时间,无法再缩短;
-
只有加快关键活动进度,才能缩短工期;非关键活动拖延不影响整体工期(在余量范围内);
-
AOE网若存在多条关键路径,必须同时优化所有关键路径,工期才会缩短。
六、写在最后
最短路径 + 关键路径,是图论章节的终极考点 。前者解决「最优路径选择」,后者解决「工程进度管控」,一个偏向算法最优解,一个偏向工程调度,覆盖了图论绝大多数实战场景。

本文三套代码均可直接编译运行,注释详尽、逻辑清晰,大家可以直接保存作为刷题模板、期末复习模板!
后续会持续更新数据结构全套干货+实战代码,建议点赞收藏,持续关注!