数据结构 · 2027 考研计算机 408

第 6 章 图

目标院校:四川大学 / 电子科技大学 | 建议用时:概念 4 小时 + 例题练习 6 小时

本章地位:图是 408 数据结构当之无愧的「大题高地」——算法大题几乎年年从这里出(遍历序列、最短路径、拓扑排序、关键路径轮流坐庄),选择题还稳定贡献 2~4 分。本章主线只有三条:①把图存起来(邻接矩阵 / 邻接表 / 十字链表 / 邻接多重表);②把图走一遍(BFS / DFS);③在图上做优化(最小生成树、最短路径、拓扑排序、关键路径)。后面四类算法在真题里全部以「手工模拟 + 填表」的形式考查,本章每个表格都配了具体数值核对 ✓,请务必拿草稿纸跟着算一遍——图的大题不是「想」出来的,是「算」出来的。

6.1 图的基本概念 高频考点

定义图 \(G\) 由顶点集 \(V\) 和边集 \(E\) 组成,记为 \(G=(V,\ E)\),其中 \(V\) 是非空有限集(\(|V|=n\),顶点数),\(E\) 是顶点间关系的有限集(\(|E|=e\),边数)。与线性表(可空)、树(可空)不同,图的顶点集不允许为空,但边集可以为空(零条边的图叫零图)。
两个基本分类
  1. 有向图 / 无向图:有向图的边是有序对,称弧,记 \(\langle v, w\rangle\)(\(v\) 为弧尾,\(w\) 为弧头,箭头指向 \(w\));无向图的边是无序对 \((v,\ w)\)。
  2. 简单图 / 多重图:若图中①不存在自环(顶点到自身的边)、②不存在重边(同一对顶点间的重复边),称为简单图;否则为多重图。408 除非特别声明,讨论的都是简单图。

6.1.1 顶点的度与握手定理

度无向图中顶点 \(v\) 的度 \(TD(v)\) 是与 \(v\) 关联的边数。有向图中分两半:以 \(v\) 为弧头的弧数叫入度 \(ID(v)\),以 \(v\) 为弧尾的弧数叫出度 \(OD(v)\),且 \(TD(v)=ID(v)+OD(v)\)。
握手定理无向图所有顶点度数之和等于边数的两倍: \[ \sum_{i=1}^{n}TD(v_i)=2e \] 有向图中,入度总和 = 出度总和 = 弧数:\(\sum ID(v_i)=\sum OD(v_i)=e\)。
数值验证:三角形(\(n=3,\ e=3\))每个顶点度都是 2,度和 \(2+2+2=6=2\times3\) ✓;有向环 \(\langle v_1,v_2\rangle,\langle v_2,v_3\rangle,\langle v_3,v_1\rangle\)(\(e=3\))每点入度 = 出度 = 1,\(\sum ID=3=\sum OD=e\) ✓。
一句话记忆每条无向边给两端各贡献 1 度(共 2),每条弧给弧尾贡献 1 出度、给弧头贡献 1 入度——所以「度和必为偶数」「入度和 = 出度和」是两组天然的整除性检验器,选择题先拿它们排除错误选项。
例 1 高频考点 度与边数的计算

设无向图 \(G\) 有 12 条边,其中度数为 3 的顶点有 6 个,其余顶点的度数均小于 3,则 \(G\) 至少有多少个顶点?

查看解答

由握手定理,\(\sum TD=2e=24\)。6 个 3 度顶点占去 \(6\times3=18\),剩余度数和 \(6\);其余顶点每个度数 \(\le 2\)(且属于图至少 1 度),要顶点最少就让每个顶点尽量吃满 2 度:\(6\div2=3\) 个。故至少 \(6+3=\)9 个顶点。核对:\(6\times3+3\times2=18+6=24=2\times12\) ✓。

易错:别写成 \(24\div2=12\) 个顶点——度数和除以「平均度」没有意义;正确思路是「剩余度和 ÷ 每点度上限」向上取整。

6.1.2 完全图、连通与树图

完全图的边数无向完全图:任意两点间恰一条边,\(n\) 个顶点共 \[ e=\frac{n(n-1)}{2} \] 有向完全图:任意两点间恰一对相反弧,共 \(e=n(n-1)\)(每个顶点出度 = 入度 = \(n-1\))。
数值验证:无向 \(n=5\):\(\frac{5\times4}{2}=10\) 条,每点度 4,度和 \(5\times4=20=2\times10\) ✓;有向 \(n=4\):\(4\times3=12\) 条弧,每点 \(TD=6\),度和 \(4\times6=24=2\times12\) ✓。
连通性术语
  1. 路径:顶点序列 \(v_p,\dots,v_q\) 相邻顶点间都有边;路径长度 = 路上边(弧)的条数;顶点不重复的路径叫简单路径;首尾相同的路径叫回路(环)。
  2. 连通(无向):任意两顶点之间有路径;图中的极大连通子图叫连通分量。
  3. 强连通(有向):任意两顶点之间互相可达(\(v\to w\) 且 \(w\to v\));极大强连通子图叫强连通分量。
  4. 生成树:连通图的包含全部 \(n\) 个顶点的极小连通子图(\(n-1\) 条边);非连通图各分量的生成树构成生成森林。
「极大 / 极小」辨析连通分量是极大连通子图——再加任何一个顶点(及其边)就不连通,强调「装得下就都装」;生成树是极小连通子图——再去掉任何一条边就不连通,强调「一条不多余」。极大极小说的是包含关系,不是重要性。
树图的三位一体对 \(n\) 个顶点的无向图 \(G\),以下命题等价(可当判别器用): \[ G\ \text{连通且无环}\ \Longleftrightarrow\ G\ \text{连通且}\ e=n-1\ \Longleftrightarrow\ G\ \text{无环且}\ e=n-1\ \Longleftrightarrow\ \text{任意两点间路径唯一} \] 数值验证(\(n=5,\ e=4\),连通):如边集 \(\{12,13,14,15\}\)(星形),无环 ✓,任两点路径唯一 ✓;若再加一条边 \(23\) 变 \(e=5\),立刻出现回路 1-2-3-1,不再是无环 ✓——「多一条必成环,少一条必断开」。
例 2 真题风格 「保证连通」的最少边数

具有 \(n\) 个顶点的无向图,若要在任何情况下(无论边怎样分布)都保持连通,至少需要多少条边?( )
A. \(n-1\) B. \(\dfrac{n(n-1)}{2}\) C. \(\dfrac{(n-1)(n-2)}{2}+1\) D. \(\dfrac{n(n-1)}{2}+1\)

查看解答

C。反过来想:边最多的不连通图长什么样?把 \(n-1\) 个顶点做成完全图、剩 1 个孤立点,此时恰不连通且边数最大:

\[ e_{\max}(\text{不连通})=\frac{(n-1)(n-2)}{2} \]

再添 1 条边必然碰到孤立点,图必连通。故答案 \(\frac{(n-1)(n-2)}{2}+1\)。数值验证(\(n=4\)):\(\frac{3\times2}{2}+1=4\)。只有 3 条边时可以摆成三角形 + 孤立点(不连通);有 4 条边时若仍不连通,最多 3 条(划分 3+1),矛盾 ✓。A 错:\(n-1\) 条边只有在「恰好构成树」这一种摆法下才连通,「任何情况下」不成立。

练习 1 易错

判断正误:(1) 有向图中所有顶点的入度之和等于出度之和;(2) \(n\) 个顶点(\(n\ge2\))的强连通图中,每个顶点的入度和出度都不小于 1;(3) \(n\) 个顶点的无向图若有 \(\frac{(n-1)(n-2)}{2}+1\) 条边,则一定连通。

查看答案

(1) 对:每条弧同时贡献 1 出度 1 入度,\(\sum ID=\sum OD=e\)。

(2) 对:强连通要求每个点既有指向它的路径(入度 \(\ge1\))又有它出发的路径(出度 \(\ge1\))。

(3) 对:这正是例 2 的结论——超过「最大不连通边数」必连通。

练习 2

分别计算 \(n=6\) 时无向完全图与有向完全图的边数,并用握手定理核对。

查看答案

无向 \(\frac{6\times5}{2}=15\) 条:每点度 5,度和 \(6\times5=30=2\times15\) ✓;有向 \(6\times5=30\) 条弧:每点入度 = 出度 = 5,\(\sum ID=\sum OD=30=e\) ✓。

6.2 图的存储结构

图的存储要同时安放「顶点」和「多对多的关系」,关系不像线性表那样能靠位置隐含,必须显式记录。主角是两种:邻接矩阵(顺序存储思路)与邻接表(链式存储思路),另有两个改进型号(十字链表、邻接多重表)专门修补它们各自的痛点——「每种结构解决了什么问题」正是选择题的出题点。

6.2.1 邻接矩阵与邻接表

邻接矩阵用 \(n\times n\) 矩阵 \(A\) 存边:\(A[i][j]=1\) 表示有 \(\langle v_i,v_j\rangle\)(无向图 \((v_i,v_j)\));带权图存权值 \(w\),不存在记 \(\infty\)(或 0,按题意),对角线为 0。核心性质:
  1. 无向图的邻接矩阵是对称矩阵(\(A=A^{\mathrm T}\),边 \((i,j)\) 同时落在 \([i][j]\) 与 \([j][i]\));有向图一般不对称。
  2. 无向图顶点 \(v_i\) 的度 = 第 \(i\) 行(或列)非零(非 \(\infty\))元个数;有向图出度看第 \(i\) 行,入度看第 \(i\) 列。
  3. 判断任意两顶点是否邻接、读权值都是 \(O(1)\)。
  4. 空间 \(O(n^{2})\),与边数无关——稠密图合算,稀疏图浪费。

// 邻接矩阵存储(严蔚敏教材风格)

#define MAX_VERTEX_NUM 100
typedef char VertexType;                    // 顶点数据类型
typedef int  EdgeType;                      // 带权图:权值类型
typedef struct {
    VertexType vexs[MAX_VERTEX_NUM];        // 顶点表
    EdgeType   arcs[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; // 邻接矩阵
    int vexnum, arcnum;                     // 顶点数、边(弧)数
} MGraph;   // 无向图加边要对称写两格:arcs[i][j] = arcs[j][i] = w
邻接表顶点表(顺序)+ 每个顶点挂一条边表(单链表),链表结点记录邻接点下标。核心性质:
  1. 空间 \(O(n+e)\);无向图每条边被存两次(两端各一个边结点),边结点共 \(2e\) 个;有向图只存一次,天然是「出边表」。
  2. 有向图求出度容易(数一下边表长度),求入度难——必须遍历全部 \(n\) 条边表(\(O(n+e)\)),或另建逆邻接表。
  3. 判断 \(i\)、\(j\) 是否邻接要顺着边表找,\(O(\text{该点度})\),不如矩阵快;适合稀疏图,后面 BFS / DFS 用它能拿到更好的复杂度。
0 1 2 3 4 5 无向图 G(n=6,e=7) 邻接表(边表按编号升序) 0 12∧ 1 034∧ 2 04∧ 3 15∧ 4 125∧ 5 34∧
图 6-1 无向图 G 及其邻接表:边表按邻接点编号升序链接(顶点 1 的边表长 3 = 其度,7 条边共 14 个边结点 = 2e ✓)

// 邻接表存储(边结点 + 顶点结点 + 图)

typedef struct ArcNode {                  // 边(弧)结点
    int adjvex;                           // 该边指向的邻接点下标
    struct ArcNode *nextarc;              // 指向依附于同一顶点的下一条边
    int weight;                           // 权值(带权图才需要)
} ArcNode;
typedef struct VNode {                    // 顶点结点
    VertexType data;                      // 顶点信息
    ArcNode *firstarc;                    // 该顶点边表的头指针
} VNode, AdjList[MAX_VERTEX_NUM];
typedef struct {
    AdjList vertices;                     // 顶点数组
    int vexnum, arcnum;                   // 顶点数、边数
} ALGraph;

6.2.2 十字链表、邻接多重表与对比

两个改进型号
  1. 十字链表(有向图):把邻接表(易找出边 / 出度)与逆邻接表(易找入边 / 入度)合并成一张。弧结点含两个链域:hlink 挂到「弧头相同」的下一条弧,tlink 挂到「弧尾相同」的下一条弧,同一条弧被两条链共享。解决的问题:有向图求入度难。
  2. 邻接多重表(无向图):每条边只设一个边结点,带 ilink、jlink 两个链域,同时挂在两个端点的链上。解决的问题:无向邻接表每条边存两份、标记 / 删除一条边要改两处。
存储结构适用空间求度 / 邻接解决了什么 / 软肋
邻接矩阵有向、无向皆可,宜稠密\(O(n^{2})\),与 \(e\) 无关度 = 行 / 列非零元;判邻接 \(O(1)\)判邻接快;软肋:稀疏图浪费空间
邻接表有向、无向皆可,宜稀疏\(O(n+e)\)出度易(边表长);入度要扫全表省空间、利于遍历;软肋:入度难、判邻接慢
十字链表有向图\(O(n+e)\)入度、出度都容易补上「有向图求入度难」
邻接多重表无向图\(O(n+e)\)度易求;每边仅一个结点补上「无向图每边存两份、删边两处」
易错① 邻接矩阵在「顶点编号固定」后是唯一的;邻接表不唯一——边表中邻接点的次序任意,同一张图的 DFS / BFS 序列随边表次序不同而不同,所以做题必须先约定「邻接点按编号从小到大」;
② 无向图邻接矩阵第 \(i\) 行与第 \(i\) 列的非零元个数相等(对称性),只数一处即可;
③ 十字链表对应有向图、邻接多重表对应无向图,方向别记反(「十」字有箭头方向 → 有向;「多重」两边扯平 → 无向)。
例 3 邻接表的性质辨析

具有 \(n\) 个顶点、\(e\) 条边的无向图采用邻接表存储,下列说法错误的是( )
A. 存储空间为 \(O(n+e)\) B. 求某个顶点的度只需数其边表长度 C. 判断任意两个顶点之间是否有边只需 \(O(1)\) D. 表中边结点共 \(2e\) 个

查看解答

C。判断 \(v_i\) 与 \(v_j\) 是否邻接,需在 \(v_i\) 的边表中顺链查找,最坏 \(O(\text{度}(v_i))\),不是 \(O(1)\)——\(O(1)\) 判邻接是邻接矩阵的本领。

A、B、D 均正确。用图 6-1 核对:\(n=6\) 顶点结点,边结点 \(2\times7=14\) 个 ✓;顶点 1 的边表 3 个结点,其度 = 3(关联边 0-1、1-3、1-4)✓。

练习 3 方法

有向图采用邻接表存储时,如何求某个顶点的入度?时间复杂度多少?有哪些改进方案?

查看答案

邻接表是「出边表」,入度没有现成的链——只能遍历全部 \(n\) 条边表,数 adjvex 等于该顶点下标的结点个数,时间 \(O(n+e)\)。

改进:①另建逆邻接表(按入边建链,空间翻倍);②直接改用十字链表,出入两类链共存,求入度、出度都是顺链数结点。

6.3 图的遍历 高频考点

图的遍历要解决「每个顶点可能有多条进入路径」的重复访问问题:设辅助数组 visited[],初始全 false,访问过的顶点置 true。另一件必须刻进本能的事:从某顶点出发的一次遍历,只能访问到它所在的连通分量(无向)或它能到达的部分(有向)——非连通图的完整遍历要对每个未访问顶点再启动一次。

6.3.1 BFS 与 DFS

BFS(广度优先搜索)按「波纹」扩展:先访问出发顶点,再依次访问其所有未访问邻接点,然后逐层外推。数据结构用队列(类比二叉树的层序遍历)。空间:队列最坏 \(O(n)\)。附带福利:对无权图,BFS 第一次到达某顶点经过的边数就是单源最短路径长度(第 6.5 节 Dijkstra 的特例)。

// BFS:队列 + visited 标记(邻接表版;入口先清空 visited,再对每个未访问顶点调用 BFS——非连通图多次启动)

bool visited[MAX_VERTEX_NUM];              // 全局辅助数组
void BFS(ALGraph G, int v) {
    visit(v);                              // 访问出发顶点
    visited[v] = true;                     // 入队前就标记!
    EnQueue(Q, v);
    while (!IsEmpty(Q)) {
        DeQueue(Q, v);                     // 队头顶点出队
        for (ArcNode *p = G.vertices[v].firstarc; p != NULL; p = p->nextarc)
            if (!visited[p->adjvex]) {     // 只处理未访问的邻接点
                visit(p->adjvex);
                visited[p->adjvex] = true; // 同一顶点绝不二次入队
                EnQueue(Q, p->adjvex);
            }
    }
}
代码考点「入队前标记」而不是「出队时标记」是必考细节:若出队才标记,同一顶点可能被多个邻接点重复入队,队列里出现重复,输出序列也就错了。
DFS(深度优先搜索)一条道走到黑:访问顶点后任选一个未访问邻接点深入,走不动了回溯(退回到最近一个还有未访问邻接点的顶点)。实现用递归(或显式栈),类比树的先根遍历。空间:递归工作栈 \(O(n)\)。

// DFS:递归 + 回溯(邻接表版)

void DFS(ALGraph G, int v) {
    visit(v);                              // 访问
    visited[v] = true;
    for (ArcNode *p = G.vertices[v].firstarc; p != NULL; p = p->nextarc)
        if (!visited[p->adjvex])           // 未访问才递归
            DFS(G, p->adjvex);             // 深入下一层,走完自动回溯
}
void DFSTraverse(ALGraph G) {
    for (int v = 0; v < G.vexnum; v++) visited[v] = false;
    for (int v = 0; v < G.vexnum; v++)
        if (!visited[v]) DFS(G, v);        // 非连通:调用几次就有几个分量
}

6.3.2 遍历的应用与复杂度

时间复杂度两种遍历相同,取决于存储结构:邻接表 \(O(n+e)\)(每个顶点进出队 / 栈一次,每条边被扫常数次);邻接矩阵 \(O(n^{2})\)(每个顶点都要扫完矩阵一整行 \(n\) 个格子)。空间(队列 / 递归栈)均为 \(O(n)\)。
遍历的三大应用①连通分量计数:DFSTraverse 中 DFS 被启动的次数 = 无向图连通分量个数;②判连通:一次 DFS 后 visited 全为 true ⟺ 图连通;③判两点连通 / 可达(有向图判可达是经典大题雏形)。序列不唯一时按「邻接点编号小者优先」约定书写。
例 4 真题风格 写遍历序列

对图 6-1 的无向图 G(邻接表边表按编号升序),(1) 从顶点 0 出发写出 DFS 序列与 BFS 序列;(2) 若删除边 (0,1) 与 (0,2),再从顶点 0 出发遍历,结果如何?

查看解答

(1) DFS:visit(0) → 邻接点 1 → visit(1) → 1 的邻接点 0 已访问,走 3 → visit(3) → 3 的邻接点 1 已访问,走 5 → visit(5) → 5 的邻接点 4 → visit(4) → 4 的邻接点 1 已访问,走 2 → visit(2),全部回溯结束,序列为 \(0,\ 1,\ 3,\ 5,\ 4,\ 2\)。

BFS:访问 0,入队 1、2 → 出队 1,入队 3、4 → 出队 2(邻接点 0、4 均已标记)→ 出队 3,入队 5 → 出队 4(1、2、5 均已标记)→ 出队 5,序列为 \(0,\ 1,\ 2,\ 3,\ 4,\ 5\)。逐点核对:访问顶点恰 6 个、每条边最多被碰 2 次(无向图两边表各一次),与 \(O(n+e)\) 一致 ✓。

(2) 删边后 0 成孤立点:从 0 出发的 DFS、BFS 都只输出「0」便结束——剩余 5 个顶点属于另一个连通分量,必须由外层循环重新启动才能访问。核对:此时连通分量 2 个,遍历函数启动次数也是 2 次 ✓。

例 5 高频考点 DFS 递归代码补全

// 补全下面 DFS 程序的三个空

void DFS(ALGraph G, int v) {
    visit(v);
    visited[v] = ________;                     // ①
    for (ArcNode *p = G.vertices[v].firstarc; p != NULL; p = p->nextarc)
        if (________)                          // ②
            DFS(G, ________);                  // ③
}
查看解答

① true——访问后立刻标记,防止重复访问;② !visited[p->adjvex]——只对未访问邻接点递归;③ p->adjvex——沿当前边深入邻接点。

追问两个变体:若漏掉①,同一顶点被反复递归,无限递归直到栈溢出;若把②的条件写反(写成对已访问点递归),同样死循环。三个空合起来一句话:「先标记、再挑新的、往深处走」。

再问复杂度:邻接表 \(O(n+e)\),邻接矩阵 \(O(n^{2})\);递归栈深不超过 \(n\)。

练习 4 易错

(1) 具有 \(n\) 个顶点、\(e\) 条边的图分别用邻接表、邻接矩阵存储,BFS 的时间复杂度各是多少?(2) 对有向图,从某顶点出发一次 DFS 能访问所有顶点,能断定该图强连通吗?

查看答案

(1) 邻接表 \(O(n+e)\),邻接矩阵 \(O(n^{2})\)。

(2) 不能。一次 DFS 全访问只说明该顶点到其余各点可达(单向),强连通要求互相可达。反例:弧 \(\langle 0,1\rangle,\langle 0,2\rangle,\langle 1,2\rangle\),从 0 出发 DFS 访问全部 3 个顶点,但没有任何回到 0 的弧,非强连通。正确判法:从每个顶点各做一次 DFS(或一次 DFS + 一次逆图 DFS)。

6.4 最小生成树 高频考点

最小生成树(MST)连通带权图 \(G\) 的所有生成树中,边权之和最小的那棵叫最小生成树。Prim 与 Kruskal 都是贪心算法,理论根基是 MST 性质:把顶点集划分成 \(U\) 与 \(V-U\) 两堆,所有跨堆边中权值最小的那条必在某棵 MST 中。
4 1 2 3 5 2 6 4 0 1 2 3 4 5 连通带权无向图:n = 6,e = 8,红色数字为边权 w(i,j)
图 6-2 6 顶点连通带权无向图(本节 MST 与 6.5 节 Dijkstra 共用此图;无向边视作双向可达)

6.4.1 Prim:逐步加顶点

Prim 算法维护已选顶点集 \(U\):每轮在跨过割 \((U,\ V-U)\) 的所有边中挑权最小的一条,把边和新顶点一起收进来,共选 \(n-1\) 轮。逐步加顶点,树始终连成一片。时间 \(O(n^{2})\)(两重循环扫 \(n\) 个顶点),与边数无关 → 适合稠密图。

对图 6-2 从 \(v_0\) 出发手工模拟(候选边按「权值最小,权同取两端编号小者」):

轮次已选集合 \(U\)当前候选跨割边(权)选中边权
初始\(\{0\}\)(0,1):4 (0,2):1——
1\(\{0,2\}\)(0,1):4 (2,1):2 (2,4):5(0,2)1
2\(\{0,1,2\}\)(1,3):3 (2,4):5(2,1)2
3\(\{0,1,2,3\}\)(3,4):2 (3,5):6 (2,4):5(1,3)3
4\(\{0,1,2,3,4\}\)(4,5):4 (3,5):6(3,4)2
5全部 6 点—(4,5)4

得 MST 边集 \(\{(0,2),(2,1),(1,3),(3,4),(4,5)\}\),权和 \(1+2+3+2+4=\)12(恰 \(n-1=5\) 条 ✓)。

6.4.2 Kruskal:逐步加边

Kruskal 算法把 \(e\) 条边按权升序排序,依次扫描:若当前边两端点不在同一连通分量(用并查集查,近乎 \(O(1)\))就选入,否则弃掉(会成环),选满 \(n-1\) 条为止。时间 \(O(e\log_2 e)\)(瓶颈在排序)→ 适合稀疏图。

对图 6-2 手工模拟(排序后依次:(0,2)1、(2,1)2、(3,4)2、(1,3)3、(0,1)4、(4,5)4、(2,4)5、(3,5)6):

次序当前边(权)判定选后连通分量状态
1(0,2):1选\(\{0,2\}\)、\(\{1\}\)、\(\{3\}\)、\(\{4\}\)、\(\{5\}\)
2(2,1):2选\(\{0,1,2\}\)、\(\{3\}\)、\(\{4\}\)、\(\{5\}\)
3(3,4):2选\(\{0,1,2\}\)、\(\{3,4\}\)、\(\{5\}\)
4(1,3):3选\(\{0,1,2,3,4\}\)、\(\{5\}\)
5(0,1):4弃(0、1 已同分量,成环)不变
6(4,5):4选全部合并,已选 \(5=n-1\) 条,结束

边集与 Prim 完全一致,权和同为 12 ✓——同一图两法殊途同归。

MST 唯一性① 若图中各边权互不相同,则 MST 唯一(充分条件);② 存在等权边时 MST 可能不唯一(但各棵 MST 的权和我恒相等);③「权互不相同」不是必要的:如图 6-2 有两对等权边((2,1) 与 (3,4) 同为 2,(0,1) 与 (4,5) 同为 4),但逐轮检查可知其 MST 唯一。判断「唯一」的可靠办法是把模拟做完,看每一步是否有权值相同的替代边。
练习 5 易错 方法

(1) 判断:① MST 必包含全图权值最小的那条边;② 若各边权互不相同,则 MST 唯一;③ 图中存在等权边时,MST 一定不唯一;④ Prim 与 Kruskal 求出的 MST 边集总相同。
(2) 对图 6-2 改从顶点 5 出发执行 Prim,写出依次加入的顶点与边,并验证所得 MST 边集是否与正文从 0 出发时相同。

查看答案

(1) ① 对:最小权边是某个割的最小跨割边,由 MST 性质必在某棵 MST 中;② 对:唯一性的标准充分条件;③ 错:「可能不唯一」≠「一定不唯一」,图 6-2 就是等权但唯一的反例;④ 错:MST 不唯一时两种贪心可能落入不同的(权和相同的)MST,MST 唯一时边集才必然相同。

(2) \(\{5\}\):选 (5,4)(4);\(\{4,5\}\):选 (4,3)(2);\(\{3,4,5\}\):选 (3,1)(3);\(\{1,3,4,5\}\):选 (1,2)(2);最后选 (2,0)(1)。加顶点顺序 \(5\to4\to3\to1\to2\to0\),权和 \(4+2+3+2+1=12\) ✓,边集与从 0 出发完全相同——起点只改变构造过程,MST 唯一时结果必相同。

6.5 最短路径 高频考点

6.5.1 Dijkstra:逐步填表

Dijkstra 算法求单源最短路径(一个源点到其余各点),时间 \(O(n^{2})\)。维护两个数组:\(dist[i]\) =「目前所知源点到 \(i\) 的最短路径长」(暂定值),\(prev[i]\) = 该路径上 \(i\) 的前驱。每轮三步:
  1. 在未确定的顶点中挑 \(dist\) 最小者 \(u\)(多个同小取编号小者),将 \(u\) 最终确定;
  2. 用 \(u\) 松弛其邻接点:若 \(dist[u]+w(u,j)\lt dist[j]\),更新 \(dist[j]\) 与 \(prev[j]\);
  3. 已确定的顶点永不回头再更新;不可达记 \(\infty\)。

对图 6-2(无向边双向可走)从 \(v_0\) 出发手工填表(格子记「路径长(前驱)」,加粗=本轮确定):

轮次确定\(dist[1]\)\(dist[2]\)\(dist[3]\)\(dist[4]\)\(dist[5]\)
初始—4 (0)1 (0)\(\infty\)\(\infty\)\(\infty\)
1\(v_2\)3 (2)1\(\infty\)6 (2)\(\infty\)
2\(v_1\)3—6 (1)6 (2)\(\infty\)
3\(v_3\)——66 (2)12 (3)
4\(v_4\)———610 (4)
5\(v_5\)————10

最终 \(dist=[0,3,1,6,6,10]\),\(prev=[-,2,0,1,2,4]\)。逐条核对:\(0\to2=1\) ✓;\(0\to2\to1=1+2=3\)(比直连 4 更短)✓;\(0\to2\to1\to3=3+3=6\) ✓;\(0\to2\to4=1+5=6\) ✓;\(0\to2\to4\to5=6+4=10\)(第 4 轮把 12 改成 10)✓。

填表铁律① 直接边只是「初始暂定值」,后续可能被绕行路径改小(如 \(dist[1]\):4→3;\(dist[5]\):12→10);② 第 3 轮 \(dist[3]=dist[4]=6\) 并列,按约定取小的下标 \(v_3\),换一种取法最终结果不变(先定 \(v_4\) 时 \(v_3\) 仍为 6);③ 每轮只确定一个顶点,确定的格子抄下来不再看。
例 6 高频考点 Dijkstra 填表(大题)

有向带权图:顶点 \(v_0\sim v_4\),弧 \(\langle0,1\rangle{:}10\)、\(\langle0,3\rangle{:}30\)、\(\langle0,4\rangle{:}100\)、\(\langle1,2\rangle{:}50\)、\(\langle2,4\rangle{:}10\)、\(\langle3,2\rangle{:}20\)、\(\langle3,4\rangle{:}60\)。从 \(v_0\) 出发执行 Dijkstra,给出每轮 \(dist\) 数组与最终最短路径。

查看解答

初始:\(dist=[0,10,\infty,30,100]\)(直接弧 3 条)。

轮确定\(dist[1]\)\(dist[2]\)\(dist[3]\)\(dist[4]\)说明
1\(v_1\) (10)106030100松弛 \(\langle1,2\rangle\):\(10+50=60\lt\infty\)
2\(v_3\) (30)—503090松弛 \(\langle3,2\rangle\):\(30+20=50\lt60\);\(\langle3,4\rangle\):\(30+60=90\lt100\)
3\(v_2\) (50)—50—60松弛 \(\langle2,4\rangle\):\(50+10=60\lt90\)
4\(v_4\) (60)———60全部确定

最终 \(dist=[0,10,50,30,60]\)。最短路径:\(v_0\to v_1=10\) ✓;\(v_0\to v_3=30\) ✓;\(v_0\to v_3\to v_2=30+20=50\) ✓;\(v_0\to v_3\to v_2\to v_4=50+10=60\) ✓。

易错:\(dist[4]\) 一路被改三次(100→90→60),直连弧最长反而是「假象」;漏掉某轮松弛是填表大题的头号失分点。

6.5.2 Floyd 与负权问题

Floyd 算法求每一对顶点间的最短路径。核心递推(\(A^{(k)}[i][j]\) 表示只允许经过前 \(k\) 个顶点中转时的最短距离): \[ A^{(k)}[i][j]=\min\big(A^{(k-1)}[i][j],\ A^{(k-1)}[i][k]+A^{(k-1)}[k][j]\big),\qquad A^{(-1)}\ \text{即邻接矩阵} \] 三重循环、中转点 \(k\) 必须放最外层。时间 \(O(n^{3})\)、空间 \(O(n^{2})\),实现只有五行。

// Floyd 核心:中转点 k 在最外层

for (int k = 0; k < n; k++)              // 依次放开每个中转点
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            if (A[i][k] + A[k][j] < A[i][j])
                A[i][j] = A[i][k] + A[k][j];   // i→k→j 更短则改道
负权边Dijkstra 不能处理负权边:反例——弧 \(\langle0,2\rangle{:}1\)、\(\langle0,1\rangle{:}2\)、\(\langle1,2\rangle{:}-2\)。第 1 轮按 \(dist[2]=1\lt dist[1]=2\) 先确定 \(v_2=1\);随后确定 \(v_1\) 时发现 \(2+(-2)=0\lt1\),但「已确定不回头」,Dijkstra 报 1,正确答案是 0,出错。
Floyd 允许负权边,但不允许负权回路(沿回路转一圈权和为负,每转一圈更短,最短路径不存在)。另外 Floyd 还能顺便判回路:跑完后若某 \(A[i][i]\lt0\),说明存在负回路。
例 7 易错 两个算法的边界

下列说法正确的是( )
A. Dijkstra 算法在存在负权边的图上得到的结果可能出错 B. Floyd 算法只适用于无向图 C. Floyd 算法允许图中存在负权回路 D. Dijkstra 算法每一轮确定的顶点,之后仍可能被更短的路径取代

查看解答

A。正是上文反例的结论。B 错:Floyd 对有向、无向图都适用;C 错:负权回路上距离可无限变小,最短路径无定义,Floyd 前提是不含负权回路;D 错:「已确定不再更新」是 Dijkstra 的铁律,写代码时不许回头碰已确定集合。

6.6 拓扑排序与关键路径 高频考点

6.6.1 AOV 网与拓扑排序

AOV 网用顶点表示活动、弧表示活动间优先关系(\(\langle i,j\rangle\) 表示活动 \(i\) 必须先于活动 \(j\))的有向无环图。AOV 网必然无环——有环意味着某活动要等自己完成才能开始,逻辑死锁。拓扑排序:把全部顶点排成线性序列,使每条弧的起点都排在终点之前。
Kahn 入度法(必会)重复两步直到输出全部 \(n\) 个顶点:
  1. 在图中选一个入度为 0 的顶点输出(多个任选 → 拓扑序列不唯一);
  2. 删除它及它发出的全部弧(其全部后继入度减 1)。
时空:\(O(n+e)\)(邻接表)。若循环结束输出顶点数 小于 \(n\),说明剩下的顶点入度都非 0、互相成环——这是判断有向图有环的标准方法。逆拓扑序列 = 把任一拓扑序列倒置(或对逆邻接表做同样过程)。
例 8 真题风格 拓扑序列合法性

AOV 网:顶点 \(1\sim6\),弧集 \(\{\langle1,2\rangle,\langle1,3\rangle,\langle2,4\rangle,\langle3,4\rangle,\langle3,5\rangle,\langle4,6\rangle,\langle5,6\rangle\}\)。下列序列中不是合法拓扑序列的是( )
A. 1,2,3,4,5,6 B. 1,3,5,2,4,6 C. 1,2,4,3,5,6 D. 1,3,2,5,4,6

查看解答

C。逐项检查每条弧「起点是否在终点前」:A 全部弧满足,合法(Kahn 过程:1 出 → 2,3 入 0 → 2 出 → 3 出 → 4,5 → 6)✓;B:1,3,5,2,4,6 —— 5 只依赖 3,可在 2 前输出,合法 ✓;C:1,2,4,3,…——\(\langle3,4\rangle\) 要求 3 在 4 之前,此处 4 抢先,不合法;D:1,3,2,5,4,6 逐弧核对全部满足,合法 ✓。本图合法拓扑序列共 5 个(另两个是 1,2,3,5,4,6 与 1,3,2,4,5,6),印证「拓扑序列不唯一」。

6.6.2 AOE 网与关键路径

AOE 网用弧(边)表示活动、权表示活动持续时间的带权有向无环图;顶点是事件(瞬间发生,标志入弧活动全部完成)。唯一入度 0 的顶点叫源点,唯一出度 0 的叫汇点。关键路径 = 源点到汇点的最长路径,其长度即总工期;关键活动 = 关键路径上的活动。
四个量设活动 \(a=\langle v_i\to v_j\rangle\),权 \(w\):
  1. 事件最早发生时间(正推取大):\(VE(v_j)=\max\{VE(v_i)+w_{ij}\}\),源点 \(VE=0\);
  2. 事件最迟发生时间(反推取小):\(VL(v_i)=\min\{VL(v_j)-w_{ij}\}\),汇点 \(VL=\) 工期;
  3. 活动最早开始:\(e(a)=VE(v_i)\)(弧尾事件一发生即可开工);活动最迟开始:\(l(a)=VL(v_j)-w\)(再晚就耽误弧头事件);
  4. 时间余量 \(l(a)-e(a)=0\) 的活动即关键活动;关键活动连成关键路径。
a₁=3a₃=4a₅=6 a₂=1a₄=5a₈=3 a₆=2a₇=4 v₁ v₂ v₃ v₄ v₅ v₆ 0 | 07 | 713 | 13 3 | 39 | 91 | 2
图 6-3 AOE 网(顶点下方「m | n」为 VE | VL;红色粗弧为关键活动,两条关键路径 v₁→v₂→v₄→v₅→v₆ 与 v₁→v₂→v₅→v₆,工期 13)
例 9 真题风格 大题高地 关键路径全表计算

对图 6-3 的 AOE 网:(1) 填出各事件 \(VE\)、\(VL\);(2) 填出各活动的 \(e\)、\(l\)、\(l-e\),指出全部关键活动与关键路径;(3) 将 a₃ 缩短 1 天,总工期变为多少?将 a₇ 缩短 1 天呢?

查看解答

(1) 正推(取大):\(VE(v_1)=0\);\(VE(v_2)=0+3=3\);\(VE(v_3)=0+1=1\);\(VE(v_4)=\max\{3+4,\ 1+5\}=\max\{7,6\}=7\);\(VE(v_5)=\max\{3+6,\ 7+2\}=\max\{9,9\}=9\);\(VE(v_6)=\max\{9+4,\ 7+3\}=\max\{13,10\}=13\)。工期 13。

反推(取小,\(VL(v_6)=13\)):\(VL(v_5)=13-4=9\);\(VL(v_4)=\min\{9-2,\ 13-3\}=\min\{7,10\}=7\);\(VL(v_3)=7-5=2\);\(VL(v_2)=\min\{7-4,\ 9-6\}=\min\{3,3\}=3\);\(VL(v_1)=\min\{3-3,\ 2-1\}=\min\{0,1\}=0\)。

事件\(v_1\)\(v_2\)\(v_3\)\(v_4\)\(v_5\)\(v_6\)
\(VE\)0317913
\(VL\)0327913

(2) \(e(a)=VE(\text{弧尾})\),\(l(a)=VL(\text{弧头})-w\),逐活动计算:

a₁(\(v_1\to v_2\),3):\(e=0\),\(l=3-3=0\),\(l-e=0\),关键;a₂(\(v_1\to v_3\),1):\(e=0\),\(l=2-1=1\),余量 1;a₃(\(v_2\to v_4\),4):\(e=3\),\(l=7-4=3\),关键;a₄(\(v_3\to v_4\),5):\(e=1\),\(l=7-5=2\),余量 1;a₅(\(v_2\to v_5\),6):\(e=3\),\(l=9-6=3\),关键;a₆(\(v_4\to v_5\),2):\(e=7\),\(l=9-2=7\),关键;a₇(\(v_5\to v_6\),4):\(e=9\),\(l=13-4=9\),关键;a₈(\(v_4\to v_6\),3):\(e=7\),\(l=13-3=10\),余量 3。

关键活动 a₁、a₃、a₅、a₆、a₇,连成两条关键路径:\(v_1\to v_2\to v_4\to v_5\to v_6\)(\(3+4+2+4=13\) ✓)与 \(v_1\to v_2\to v_5\to v_6\)(\(3+6+4=13\) ✓)。

(3) a₃ 缩短 1 天:工期仍为 13——a₃ 只在第一条关键路径上,缩短后该路径变 12,但第二条仍为 13,瓶颈在另一条;a₇ 缩短 1 天:工期变 12——a₇ 是两条路径的公共活动,两条同时变 12 ✓。

套路总结:缩短工期只能动关键活动;关键路径不止一条时,必须同时缩短所有关键路径共有的活动(或各条各动一个)才见效;且活动缩短后关键路径可能转移,要重算一遍。

练习 6 易错

判断:(1) 关键路径是从源点到汇点最长的路径;(2) 任何一个关键活动缩短 1 天,总工期一定缩短 1 天;(3) AOE 网的关键路径是唯一的;(4) 事件的 \(VE\) 与 \(VL\) 相等是该事件位于所有关键路径上的必要条件。

查看答案

(1) 对:工期由最慢的路线决定,定义即「最长路径」。

(2) 错:例 9(3) 中缩短 a₃ 工期不变;只有该活动落在所有关键路径上(或关键路径唯一)时才必然见效。

(3) 错:图 6-3 就有两条。

(4) 对:某事件 \(VE\ne VL\) 说明它有正的时间余量,不可能在任何关键路径上(关键路径上每个事件、每个活动余量均为 0)。

6.7 章末自测 真题风格

限时 60 分钟,先做后看答案。难度:★★ 基础 / ★★★ 强化 / ★★★★ 冲刺。涉及手工模拟的题,请在草稿纸上把表格完整画出来再对答案。

自测 1(选择 · ★★)

无向图 \(G\) 有 12 条边,度数为 3 的顶点有 5 个,其余顶点的度数均小于 3,则 \(G\) 至少有( )个顶点。
A. 8 B. 9 C. 10 D. 11

查看答案

C。\(\sum TD=24\),5 个 3 度点占 15,余 9;其余每点度 \(\le2\),需至少 \(\lceil9/2\rceil=5\) 个,共 \(5+5=10\)。核对:\(5\times3+4\times2+1\times1=15+8+1=24=2\times12\) ✓。

自测 2(选择 · ★★★)

具有 \(n\)(\(n\ge2\))个顶点的有向强连通图,最少含多少条弧?( )
A. \(n-1\) B. \(n\) C. \(n(n-1)\) D. \(\frac{(n-1)(n-2)}{2}+1\)

查看答案

B。\(n\) 条弧首尾相接构成一个经过全部顶点的有向环即可(每点入 = 出 = 1)。\(n=3\) 验证:\(\langle1,2\rangle,\langle2,3\rangle,\langle3,1\rangle\) ✓;少于 \(n\) 条弧时必有点出度或入度为 0,无法互相可达。C 是有向完全图边数;D 是无向图保证连通的边数,都是干扰项。

自测 3(选择 · ★★★)

有向图用邻接矩阵存储,顶点 \(i\) 的出度与入度分别等于( )
A. 第 \(i\) 行非零元个数、第 \(i\) 列非零元个数 B. 第 \(i\) 列非零元个数、第 \(i\) 行非零元个数 C. 第 \(i\) 行与第 \(i\) 列非零元个数之和的一半 D. 第 \(i\) 行非零元个数与第 \(i\) 列非零元个数之差

查看答案

A。矩阵元素 \(A[i][j]=1\) 表示弧 \(\langle i,j\rangle\),从 \(i\) 发出 → 计入 \(i\) 的行(出度);射向 \(j\) → 计入 \(j\) 的列(入度)。记忆:「出行入列」。无向图因对称,行 = 列,都等于度。

自测 4(选择 · ★★★ 高频考点)

具有 \(n\) 个顶点、\(e\) 条边的图采用邻接表存储,从任一顶点出发做一次 BFS 的时间复杂度为( );采用邻接矩阵存储时为( )
A. \(O(n+e)\)、\(O(n^{2})\) B. \(O(n^{2})\)、\(O(n+e)\) C. \(O(n)\)、\(O(e)\) D. \(O(n\log_2 n)\)、\(O(n^{2})\)

查看答案

A。邻接表:每个顶点进出队一次 \(O(n)\),每条边(无向图两个边结点)被扫常数次 \(O(e)\),合计 \(O(n+e)\)。邻接矩阵:每个出队顶点都要扫一整行 \(n\) 格,\(n\times n=n^{2}\)。DFS 结论完全相同(对照 6.3 节)。

自测 5(选择 · ★★★)

对 8 个顶点的非连通无向图,从顶点 0 出发做一次 DFS,共访问了 6 个顶点,则该图连通分量数至少为( )
A. 1 B. 2 C. 6 D. 8

查看答案

B。一次 DFS 恰好覆盖一个连通分量,0 所在分量含 6 个顶点;剩下 2 个顶点无论连不连通,至少还要 1 个分量,故至少 2 个(若那 2 点互不邻接则是 3 个)。核对:遍历函数对 8 点图启动 DFS 的次数 = 分量数 ≥ 2 ✓。

自测 6(选择 · ★★★)

关于最小生成树,下列说法正确的是( )
A. Prim 算法更适合稀疏图 B. 若连通带权图各边权互不相同,则其最小生成树唯一 C. 最小生成树唯一,当且仅当各边权互不相同 D. Kruskal 算法的时间复杂度为 \(O(n^{2})\)

查看答案

B。A 反了:Prim \(O(n^{2})\) 与边数无关,适合稠密,Kruskal \(O(e\log_2 e)\) 适合稀疏。B 是标准结论 ✓。C「当且仅当」过强:等权图的 MST 也可能唯一(图 6-2 即反例)。D 错在 \(n^{2}\),应为 \(O(e\log_2 e)\)。

自测 7(选择 · ★★★ 易错)

关于最短路径算法,正确的是( )
A. Floyd 算法用于求单源最短路径 B. Dijkstra 算法能正确处理含负权边的图 C. Floyd 算法的时间复杂度为 \(O(n^{2})\) D. Floyd 算法允许负权边,但不允许负权回路

查看答案

D。A:Floyd 求每一对顶点间最短路径;B:Dijkstra 的「已确定不回头」假设会被负权边破坏(6.5.2 反例:正确 0、算出 1);C:三重循环 \(O(n^{3})\);D 正确,且跑完后 \(A[i][i]\lt0\) 可判存在负回路。

自测 8(选择 · ★★★)

有向图边集为 \(\{\langle1,2\rangle,\langle1,3\rangle,\langle2,4\rangle,\langle3,4\rangle,\langle3,6\rangle,\langle4,5\rangle,\langle6,5\rangle\}\),下列序列中不是合法拓扑序列的是( )
A. 1,2,3,4,6,5 B. 1,3,2,6,4,5 C. 1,3,6,2,4,5 D. 1,2,4,3,6,5

查看答案

D。D 中 4 排在 3 之前,违反弧 \(\langle3,4\rangle\)。A、B、C 逐弧检查「起点在前」均满足。技巧:不用重排全图,只盯每个选项里「编号乱序」的相邻对查弧。

自测 9(解答 · ★★★★)

AOE 网:事件 \(v_1\sim v_4\),活动 a₁=\(v_1\to v_2\)(2 天)、a₂=\(v_1\to v_3\)(3 天)、a₃=\(v_2\to v_4\)(4 天)、a₄=\(v_3\to v_4\)(1 天)。求各事件的 \(VE\)、\(VL\) 与各活动的 \(e\)、\(l\),指出关键路径与工期。

查看解答

正推(取大):\(VE(v_1)=0\),\(VE(v_2)=0+2=2\),\(VE(v_3)=0+3=3\),\(VE(v_4)=\max\{2+4,\ 3+1\}=6\)。反推(取小):\(VL(v_4)=6\),\(VL(v_3)=6-1=5\),\(VL(v_2)=6-4=2\),\(VL(v_1)=\min\{2-2,\ 5-3\}=\min\{0,2\}=0\)。

活动表:a₁(\(v_1\to v_2\),2):\(e=0\),\(l=2-2=0\),\(l-e=0\),关键;a₂(\(v_1\to v_3\),3):\(e=0\),\(l=5-3=2\),余量 2;a₃(\(v_2\to v_4\),4):\(e=2\),\(l=6-4=2\),关键;a₄(\(v_3\to v_4\),1):\(e=3\),\(l=6-1=5\),余量 2。

关键路径 \(v_1\to v_2\to v_4\)(\(2+4=6\) ✓),工期 6 天;a₂、a₄ 各有 2 天机动时间。

自测 10(解答 · ★★★★ 冲刺)

有向带权图:顶点 \(0\sim3\),弧 \(\langle0,1\rangle{:}10\)、\(\langle0,2\rangle{:}5\)、\(\langle1,3\rangle{:}1\)、\(\langle2,1\rangle{:}3\)、\(\langle2,3\rangle{:}2\)。从顶点 0 执行 Dijkstra,给出每轮 \(dist\) 数组、最终最短路径及路径长度。

查看解答

初始:\(dist=[0,10,5,\infty]\)(直接弧 \(\langle0,1\rangle\)、\(\langle0,2\rangle\))。

轮确定\(dist[1]\)\(dist[2]\)\(dist[3]\)说明
1\(v_2\) (5)857\(5+3=8\lt10\);\(5+2=7\lt\infty\)
2\(v_3\) (7)8—7\(v_3\) 无出弧
3\(v_1\) (8)8——松弛 \(\langle1,3\rangle\):\(8+1=9\gt7\),不改

最终 \(dist=[0,8,5,7]\):\(0\to2=5\) ✓;\(0\to2\to1=5+3=8\)(比直连 10 短)✓;\(0\to2\to3=5+2=7\)(比 \(0\to2\to1\to3=9\) 短)✓。

套路总结:Dijkstra 大题拿分三件事——每轮选对最小未确定点、松弛该点全部出弧、已确定的行不再看;表格里顺手写前驱,最后逆着 prev 还原路径。

6.8 本章考点总结

考点常考题型热度核心方法
度与边数计算选择题★★★★ 高频握手定理 \(\sum TD=2e\)、\(\sum ID=\sum OD=e\);完全图 \(\frac{n(n-1)}{2}\) / \(n(n-1)\);强连通最少 \(n\) 弧;树图 \(\Leftrightarrow\) 连通 + \(n-1\) 边
四种存储结构选择题★★★★矩阵对称性、出行入列、\(O(n^{2})\);表 \(O(n+e)\)、入度难;十字链表(有向)补入度、多重表(无向)边存一份
BFS / DFS选择 + 大题★★★★★ 每年必考队列 / 递归栈;邻接表 \(O(n+e)\)、矩阵 \(O(n^{2})\);序列按小编号约定;DFS 启动次数 = 连通分量数;入队前标记
最小生成树大题★★★★Prim 逐步加点 \(O(n^{2})\) 宜稠密;Kruskal 排序加边 + 并查集判环 \(O(e\log_2 e)\) 宜稀疏;权互不相同 ⟹ MST 唯一
最短路径大题(高频高地)★★★★★Dijkstra 每轮「定一点、松一圈、不回头」,\(O(n^{2})\),不能负权;Floyd 三层循环 \(k\) 最外 \(O(n^{3})\),可负边不可负回路
拓扑排序选择 + 大题★★★★Kahn 入度法 \(O(n+e)\);序列不唯一;输出 < \(n\) 个 ⟹ 有环;逆拓扑 = 倒置
关键路径大题★★★★★VE 正推取大、VL 反推取小;\(e=VE\)、\(l=VL-w\)、\(l-e=0\) 为关键活动;多条关键路径时需同时缩短才见效
下一步本章过关标准:四个手工模拟(Prim、Kruskal、Dijkstra、关键路径)能不看书画出完整表格并用 \(n-1\) 条边、权和、路径回代等方式自检 ✓;例题 9 道独立重做;自测 10 题至少 8 题正确;能一句话说清「十字链表和邻接多重表各解决什么」。然后进入 第 7 章 查找(B 树、散列)——线性结构上的高效检索,与本章邻接矩阵「判邻接 \(O(1)\)」的思想一脉相承。