数据结构精讲:图的最短路径与关键路径

哈喽大家好!前面我们学完了图的存储、遍历、拓扑排序,今天解锁图论最核心、考试面试必考的两大高阶难点

最短路径问题:解决"怎么走路程最短、成本最低"

关键路径问题:解决"工程最快多久完工、哪些步骤不能拖延"

这两个知识点是数据结构图论的收尾重点,也是考研、期末、算法面试的高频考点!本文全程通俗讲解,避开晦涩公式,搭配完整C++实战代码,看完直接吃透、直接复用。


一、先搞懂:两个核心问题的应用场景

1. 最短路径:最优路线问题

生活中随处可见:地图导航找最短路线、快递配送最优路径、网络数据传输最小时延。

核心定义:在带权图中,找到两个顶点之间权值和最小的路径

分为两类:

单源最短路径:一个起点,到所有终点的最短路径(Dijkstra算法)

多源最短路径:任意两点之间的最短路径(Floyd算法)

2. 关键路径:工程进度问题

多用于项目管理、工程施工、任务调度。核心是基于**AOE网(带权有向无环图)**求解。

作用:计算整个工程的最短完工时间 ,找出不可拖延的关键任务,只要关键任务延误,整体工程必然延期。


二、最短路径:Dijkstra 算法(单源最短路径)

1. 算法核心原理

适用场景 :带权有向/无向图,权值非负,求单个起点到其余所有顶点的最短路径。

核心思想:贪心算法

每次从未确定最短路径的节点中,选出距离起点最近的节点,确认其最短距离,再更新其他节点的距离,层层松弛迭代,直到所有节点遍历完成。

2. 算法执行步骤

  1. 初始化:设置起点距离为0,其余节点距离为无穷大;

  2. 选取:找到当前距离起点最近、未访问过的节点;

  3. 松弛:通过该节点中转,更新相邻节点的最短距离;

  4. 循环迭代,直到所有节点最短路径确定。

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. 四大核心参数(必考)

  1. ve[i]:事件i的最早发生时间(从源点到i的最长路径)

  2. vl[i]:事件i的最晚发生时间(不推迟工期的最晚时间)

  3. e[k]:活动k的最早开始时间

  4. l[k]:活动k的最晚开始时间

关键活动判定e[k] == l[k],所有关键活动组成的路径即为关键路径

核心结论:关键路径可能有多条,所有关键路径都走完,工程才算完工。

3. 求解步骤

  1. 拓扑排序得到顶点序列;

  2. 正向遍历拓扑序列,求所有事件的最早时间 ve;

  3. 逆向遍历拓扑序列,求所有事件的最晚时间 vl;

  4. 计算所有活动的 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. 关键路径核心结论

  1. 关键路径长度 = 整个工程的最短完工时间,无法再缩短;

  2. 只有加快关键活动进度,才能缩短工期;非关键活动拖延不影响整体工期(在余量范围内);

  3. AOE网若存在多条关键路径,必须同时优化所有关键路径,工期才会缩短。


六、写在最后

最短路径 + 关键路径,是图论章节的终极考点 。前者解决「最优路径选择」,后者解决「工程进度管控」,一个偏向算法最优解,一个偏向工程调度,覆盖了图论绝大多数实战场景。

本文三套代码均可直接编译运行,注释详尽、逻辑清晰,大家可以直接保存作为刷题模板、期末复习模板!

后续会持续更新数据结构全套干货+实战代码,建议点赞收藏,持续关注!

相关推荐
智者知已应修善业1 小时前
【51单片机一个按键切合初始流水灯按一下对半闪烁按一下显示时间】2023-10-16
c++·经验分享·笔记·算法·51单片机
晚风叙码1 小时前
堆排序建堆策略对比:向上调整与向下调整的时间复杂度分析
算法
MegaDataFlowers1 小时前
102.二叉树的层序遍历
数据结构
洛水水1 小时前
【力扣100题】28. 翻转二叉树
算法·leetcode
故事和你912 小时前
洛谷-【数据结构2-2】线段树2
开发语言·数据结构·算法·动态规划·图论
ghie90902 小时前
MATLAB 随机蛙跳算法 (SFLA) 优化最小二乘回归
算法·matlab·回归
wuweijianlove2 小时前
算法优化中的缓存层次结构与内存映射的技术7
算法
故事和你912 小时前
洛谷-【数据结构2-2】线段树1
开发语言·javascript·数据结构·算法·动态规划·图论
电科一班林耿超2 小时前
机器学习大师课 第 8 课:端到端项目实战 —— 泰坦尼克号生存预测
人工智能·算法·机器学习