第六章 图

第六章 图

6.1 图的定义和基本数据

6.1.1 图的定义

(1)图的核心定义

图是由顶点集V和顶点间的关系集合E(边的集合)组成的一种数据结构,可以用二元组定义为:G=(V,E)。

(2)无向图与有向图的定义及二元组表示

类型 核心特征 二元组表示示例 符号区别
无向图 边无方向性,无箭头标识 无向图G1=(V1,E1),其中V1={a,b,c,d},E1={(a,b),(a,c),(a,d),(b,d),(c,d)} 边集元素用小括号 ()表示,代表两点间无向边
有向图 边有方向性,用箭头标识(边称为 “弧”) 有向图G2=(V2,E2),其中V2={1,2,3},E2= 弧集元素用 ** 尖括号 <>** 表示,尖括号内前者为弧尾(箭头起点)、后者为弧头(箭头终点)

注:顶点集均用 花括号 {} 表示,仅边 / 弧集的符号随图的方向属性变化。

例如,对于图6-1所示的无向图G1和有向图G2,它们的数据结构可以描述为:G1=(V1,E1), 其中 V1={a,b,c,d},E1={(a,b),(a,c),(a,d),(b,d),(c,d)},而G2=(V2,E2),其中V2={1,2,3}, E2={<1,2>,<1,3>,<2,3>,❤️,1>}。

6.1.2 图的基本术语

(1)有向图与无向图(基础分类)

  • 有向图:边有方向性,边称为,有明确的弧尾(起点)和弧头(终点),用箭头标识。
  • 无向图:边无方向性,仅表示两点间存在连接,无箭头标识。

(2)完全图、稠密图、稀疏图

①完全图

定义:任意两个顶点之间都存在边(无向图)或弧(有向图)的图,分为无向完全图和有向完全图。

  • 无向完全图n个顶点的无向完全图,边数为组合数Cn2=n(n−1)/2(推导:任意两点仅需 1 条无向边连接,共需从n个顶点选 2 个的组合数)。例如 4 个顶点的无向完全图,边数为4×3/2=6。
  • 有向完全图n个顶点的有向完全图,弧数为排列数An2=n(n−1)(推导:两点间需 2 条方向相反的弧,弧数为无向完全图边数的 2 倍)。例如 4 个顶点的有向完全图,弧数为4×3=12。
②一般图的边 / 弧数范围
  • 无向图:顶点数为n,边数e满足0≤en(n−1)/2。
  • 有向图:顶点数为n,弧数e满足0≤en(n−1)。
③稠密图与稀疏图
  • 稠密图:边 / 弧数接近对应完全图边 / 弧数的图。
  • 稀疏图:边 / 弧数远少于对应完全图边 / 弧数的图(讲课表述中 “析出符” 为口误,实际为稀疏图)。

(3)度、入度、出度

①度的通用定义

图中一个顶点依附的边或弧的数目,称为该顶点的

②有向图的入度与出度

有向图中顶点的度为入度与出度之和,具体定义如下:

  • 入度:顶点依附的弧头(箭头指向该顶点)的数目,代表进入该顶点的弧的数量。
  • 出度:顶点依附的弧尾(箭头从该顶点出发)的数目,代表从该顶点发出的弧的数量。
③无向图的度

无向图中无入度、出度之分,顶点的度即为其相连的边的数目。

典型问题:特殊有向图的形状

若有向图中仅 1 个顶点入度为 0,其余顶点入度均为 1,则该图为有向树(入度为 0 的顶点为树根,其余顶点对应树的子节点,有唯一的双亲节点)。

(4)连通图与强连通图

类型 适用图 核心定义
连通图 无向图 图中 任意两个顶点vu 都存在从vu的路径
强连通图 有向图 图中 任意两个顶点vu 都存在双向路径(既存在vu的路径,也存在uv的路径)

反例:若无向图中存在两个顶点组(如V0V1V2V3V4V5),两组内顶点互通但组间无路径,则为非连通图;有向图中若存在顶点对无双向路径(如V1无法到V2),则为非强连通图

(5)网

图的边 / 弧上附带的相关数值称为(可表示距离、耗费等),带权的图称为网,也叫有权图。

(6)子图

设两个图G=(V,{E})和G1=(V1,{E1}),若满足V1VE1E,则称G1G的子图。例如课件中图 (a) 的顶点和边的部分子集构成的图 (b)、图 (c),均为图 (a) 的子图。

6.2 图的存储结构

6.2.1 邻接矩阵(顺序存储,底层为二维数组)

(1) 邻接矩阵的存储原理

在邻接矩阵表示中,除了存放顶点本身信息外,还用一个矩阵表示各个顶点之间的关系。若(i,j)∈E(G)或〈i,j〉∈E(G),则矩阵中第i行 第j列元素值为1,否则为0 。

图的邻接矩阵定义为:

\[A[i][j] = \begin{cases} 1 & \text{若}(i,j) \in E(G) \text{或} <i,j> \in E(G) \\ 0 & \text{其它情形} \end{cases} \]

邻接矩阵通过顶点表存储顶点本身信息,通过二维矩阵存储顶点间的邻接关系:

  • 若两顶点间存在边 / 弧,矩阵对应位置值为1(普通图)或边 / 弧的权值(网);
  • 若不存在边 / 弧,普通图对应位置为0,网对应位置为
  • 顶点自身到自身(主对角线):考试建议记为0,教材通常记为 ,需注意题目约定。

(2)不同类型图的邻接矩阵表示

①无向图的邻接矩阵

  • 构建步骤:

    ① 按顶点数构建n×n矩阵(5 顶点为5×5),初始化全为 0;

    ② 若顶点ViVj有边,将矩阵[i][j]和[j][i]置为 1(无向边双向对称)。

  • 讲课易错点:无向图 7 条边对应矩阵 14 个 1(每条边被双向记录,边数 = 矩阵中 1 的个数 / 2)。

  • 核心特点:

    1. 矩阵对称
    2. 第i行或第i 列1的个数为顶点i 的度;
    3. 完全无向图的邻接矩阵主对角线为 0,其余位置全为 1;
    4. 矩阵中1的个数的一半为图中边的数目
    5. 可通过[i][j]是否为 1,快速判断ViVj是否有边;
    6. 占用的存储单元数目为n+2e。

②有向图的邻接矩阵
  • 构建约定:矩阵记录顶点发出去的弧(出度边),即[i][j]=1表示存在<Vi,Vj>的弧。

  • 构建步骤(以4 顶点有向图为例):

    ① 构建4×4矩阵并初始化全为 0;

    ② 若Vi发出弧至Vj,仅将[i][j]置为 1(入度边不记录在该行)。

  • 核心特点:

    1. 矩阵不一定对称(有向弧单向性);
    2. 顶点i出度= 第i行 1 的个数,入度= 第i列 1 的个数,总度数 = 出度 + 入度;
    3. 矩阵中 1 的个数 = 图中弧的总数(无重复记录);
    4. 完全有向图的邻接矩阵主对角线为 0,其余位置全为 1;
    5. 很容易判断顶点i 和顶点j 是否有弧相连。

③网的邻接矩阵(带权图)
  • 课件定义公式:

    \[A[i][j] = \begin{cases} w_{ij} & \text{若}(i,j) \in E(G) \text{或} <i,j> \in E(G) \\ 0 & \text{若} i = j \\ \infty & \text{其它情形} \end{cases} \]

  • 构建注意事项(讲课重点强调):

    ① 权值wij替换普通图的 “1”,表示两顶点间边的权重;

    ② 主对角线的 0/∞需按题目要求选择(考试优先用 0);

    ③ 无向网矩阵对称,有向网矩阵不对称。

(3)邻接矩阵的优缺点(扩展补充)

  • 优点:判断两顶点是否邻接效率高(O (1));计算顶点度(无向图)、出 / 入度(有向图)方便;
  • 缺点:空间复杂度高(O (n²)),适合存储稠密图;稀疏图会造成大量空间浪费。

6.2.2 邻接表(链式存储,头节点 + 表节点)

(1)邻接表的存储结构(课件定义)

把同一个顶点发出的边链接在同一个边链表中,链表的每一个结点代表一条边,叫做表结点(边结点),邻接点域adjvex保存与该边相关联的另一顶点的顶点下标 , 链域next存放指向同一链表中下一个表结点的指针 ,数据域weight存放边的权。边链表的表头指针存放在头结点中。头结点以顺序结构存储,其数据域info存放顶点信息,链域first指向链表中第一个顶点

邻接表由头节点数组表节点链表组成,网的表节点需额外增加权值域:

节点类型 结构组成 说明
头节点 data(顶点信息)+firstarc(指向首个表节点的指针) 按顶点顺序存储,形成头节点数组
普通图表节点 adjvex(邻接点的顶点下标)+nextarc(指向下一表节点的指针) 存储顶点的出度边 / 邻接边
网的表节点 adjvex+weight(边的权值)+nextarc 比普通图表节点多权值域,记录边的权重

(2)不同类型图的邻接表表示

①无向图的邻接表

  • 构建逻辑:每个顶点的头节点链表,存储所有与该顶点邻接的顶点下标(无向边双向存储,如Vi邻接Vj,则Vi链表有j,Vj链表有i)。
  • 讲课示例:5 顶点无向图中,V1的表节点为 V2(下标 3)、V4(下标 1),顺序可任意。
  • 核心特点:
    1. 邻接表表示不唯一(表节点链入顺序可自由调整);
    2. i个头节点的链表节点数 = 顶点i
    3. 所有表节点总数 = 2× 边数(无向边双向记录);
    4. 空间复杂度为O(n+2e)(n 为顶点数,e 为边数)。
②有向图的邻接表与逆邻接表

  • 邻接表(出边表):

    ① 存储逻辑:仅记录顶点的出度边,即表节点为顶点发出弧的终点下标;

    ② 特点:第i个头节点的链表节点数 = 顶点i的出度;空间复杂度为O(n+e)(每条弧仅记录一次);无法直接求入度。

  • 逆邻接表(入边表):

    ① 存储逻辑:记录顶点的入度边,即表节点为指向该顶点的弧的起点下标;

    ② 作用:解决有向图邻接表无法直接求入度的问题,第i个头节点的链表节点数 = 顶点i的入度。

  • 讲课易错点:无特殊说明时,有向图邻接表默认指出边表

③网的邻接表
  • 构建逻辑:表节点增加weight域存储边的权值,其余结构与普通有向 / 无向图邻接表一致;

  • 讲课典型例题:已知网的邻接表重构网

    ① 核心方法:根据头节点的顶点信息,遍历每个头节点的表节点,按adjvex(邻接点下标)和weight(权值)绘制带权边;

    ② 意义:验证了存储结构可反向恢复逻辑结构,体现数据结构 “存储 - 还原” 的核心价值。

(3)邻接表的优缺点(扩展补充)

  • 优点:空间利用率高(O (n+e)),适合存储稀疏图;便于遍历顶点的邻接边;
  • 缺点:判断两顶点是否邻接效率低(需遍历对应头节点的链表);无向图存在边的重复存储。

6.2.3邻接矩阵与邻接表的核心对比

对比维度 邻接矩阵 邻接表
存储结构 二维数组(顺序存储) 头节点数组 + 链表(链式存储)
空间复杂度 O (n²)(与边数无关) 无向图 O (n+2e)、有向图 O (n+e)
邻接判断效率 O (1)(直接查矩阵下标) O (d)(d 为对应顶点的度,需遍历链表)
顶点度计算 无向图:行 / 列 1 的个数;有向图:行 = 出度、列 = 入度 无向图:链表节点数;有向图:邻接表 = 出度、逆邻接表 = 入度
适用场景 稠密图、需快速判断邻接的场景 稀疏图、需频繁遍历邻接边的场景

6.2.4讲课中的考试与学习注意事项

  1. 邻接矩阵主对角线约定:考试建议主对角线记为 0,教材常用∞,需严格遵循题目要求;
  2. 有向图邻接矩阵的边数:矩阵中 1 的个数 = 弧的总数(无向图为 1 的个数 / 2);
  3. 邻接表的唯一性:表节点顺序不影响存储有效性,因此同一图的邻接表表示不唯一;
  4. 知识衔接:掌握图的存储结构后,后续将学习图的核心操作 ——图的遍历(深度优先 / 广度优先),为后续最小生成树、最短路径等应用打基础。

6.3 图的遍历

6.3.1图的遍历概述

(1)定义

从图中某一顶点出发,访遍图中其余所有顶点且每个顶点仅被访问一次的操作,称为图的遍历。

(2)核心意义

图的遍历算法是求解图的连通性问题、拓扑排序、关键路径等后续图应用算法的基础,是图论算法体系的核心前置知识点。

(3)两种经典遍历方法

深度优先搜索(DFS)遍历、广度优先搜索(BFS)遍历,两种方法均可实现对图中所有顶点的不重复访问,且遍历序列不唯一。

6.3.2 深度优先搜索(DFS)遍历

(1)算法核心思想

类似于树的先序遍历,遵循 “先深后广、回溯探索” 的原则,优先沿着一条路径访问到尽头,再回溯到上一顶点探索其他分支。

(2)算法执行步骤(课件标准流程)

  1. 首先访问顶点i,并将其访问标记置为已访问(即visited[i]=1,初始化时所有顶点visited值为 0);
  2. 搜索与顶点i有边相连的下一个顶点j,若j未被访问,则访问j并标记visited[j]=1,再从j开始重复此探索过程;若j已访问,则继续查找与i相连的其他顶点;
  3. 若与i相连的所有顶点均已访问,回溯到上一个访问的顶点,重复上述探索过程,直至图中所有顶点都被访问完毕。

(3)辅助存储结构

遍历过程需借助实现回溯,栈遵循 “后进先出” 原则,用于记录访问路径,当当前顶点分支探索完毕时,从栈中弹出顶点实现回溯。

(4)典型示例(无向图 G7,从顶点 0 出发)

例如,对所示无向图G7,从各个顶点出发的深度优先搜索遍历序列可有多种。
在无向图G7中,从顶点0出发的深度优先搜索遍历序列举如下:

  1. 初始访问顶点 0,标记visited[0]=1,选择其邻接顶点 1 进行访问,标记visited[1]=1
  2. 从顶点 1 出发,选择未访问的邻接顶点 4,标记visited[4]=1,再从 4 出发访问邻接顶点 5,标记visited[5]=1
  3. 顶点 5 的邻接顶点(4、1)均已访问,回溯至顶点 4,其邻接顶点(1、5)也已访问,继续回溯至顶点 1;
  4. 顶点 1 剩余未访问邻接顶点 6,访问 6 并标记visited[6]=1,再从 6 出发访问未访问的邻接顶点 2,标记visited[2]=1
  5. 顶点 2 的邻接顶点(0、6)均已访问,依次回溯至 6、1,再回溯至顶点 0;
  6. 顶点 0 剩余未访问邻接顶点 3,访问 3 并标记visited[3]=1,至此所有顶点访问完毕。
    • 其中一个合法遍历序列:0→1→4→5→6→2→3(序列不唯一)。

(5)关键结论与考点

  1. 遍历起点可任意选择,考试中常指定起点;
  2. 同一顶点的多个未访问邻接顶点可任选访问顺序,因此DFS 遍历序列不唯一
  3. 考试常考 “判断给定序列是否为合法 DFS 遍历序列”,需验证序列是否符合 “先深后广、回溯探索” 的逻辑。

6.3.3 广度优先搜索(BFS)遍历

(1)算法核心思想

遵循 “先广后深、分层访问” 的原则,先访问起始顶点的所有邻接顶点(同层顶点),再依次访问各邻接顶点的邻接顶点(下一层顶点)。

(2)算法执行步骤(课件标准流程)

  1. 初始化队列并置空,访问起始顶点后将其入队;
  2. 若队列非空,取出队头顶点,依次访问其所有未被访问的邻接顶点,将这些顶点标记为已访问并入队;
  3. 重复步骤 2,直至队列为空;若此时仍有未访问顶点,需另选起点重复遍历;
  4. 队列为空时,本次遍历结束。

(3)辅助存储结构

遍历过程需借助队列实现分层访问,队列遵循 “先进先出” 原则,保证同层顶点按入队顺序依次处理。

(4)典型示例(无向图 G7,从顶点 1 出发)

例如,对下图所示无向图G7,从顶点1出发的广度优先搜索遍历序列可有多种,下面仅给出三种,其它可作类似分析。
在无向图G7中,从顶点1出发的广度优先搜索遍历序列举如下:

  1. 初始访问顶点 1,入队;出队后访问其所有未访问邻接顶点 2、3(可互换顺序),标记后入队;
  2. 队头顶点 2 出队,访问其未访问邻接顶点 4、5(可互换顺序),标记后入队;
  3. 队头顶点 3 出队,访问其未访问邻接顶点 6、7(可互换顺序),标记后入队;
  4. 队头顶点 4 出队,访问其未访问邻接顶点 8,标记后入队;
  5. 依次处理队中剩余顶点 5、6、7、8,均无未访问邻接顶点,队列最终为空,遍历完成。
    • 合法遍历序列示例:1→2→3→4→5→6→7→8、1→3→2→7→6→5→4→8 等(序列不唯一)。

例如,对下图所示无向图G7,从顶点1出发的广度优先搜索遍历序列可有多种,下面仅给出三种,其它可作类似分析。
在无向图G7中,从顶点1出发的广度优先搜索遍历序列举三种为:
1, 2, 3, 4, 5, 6, 7, 8
1, 3, 2, 7, 6, 5, 4, 8
1, 2, 3, 5, 4, 7, 6, 8

(5)快速判断合法 BFS 序列的技巧

可将遍历序列按 “层” 划分:起始顶点为第 0 层,其邻接顶点为第 1 层,第 1 层顶点的邻接顶点为第 2 层,以此类推。同层顶点的顺序可任意互换,只要满足 “先访问上层顶点,再访问下层顶点” 即可判定为合法序列。

6.4 图的应用.

6.4.1生成树和最小生成树

(1)核心思想:复杂问题简化(图→树)

计算机解决复杂结构问题的通用逻辑:多对多的图→一对多的树→线性结构,其中树可进一步简化为二叉树(任意树可与二叉树相互转换,森林可拆解为多棵树再转二叉树),实现难→易的问题拆解。

(2)生成树的基础概念

图与树的本质区别
  • 树:一对多的层次结构,有唯一根节点(无入边),无回路;
  • 图:多对多的网状结构,无固定根节点,可存在回路。
生成树的定义

在图论中,常常将树定义为一个无回路连通图。例如,图6-18中的两个图就是无回路的连通图。乍一看它似乎不是不是树,但只要选定某个顶点做根并以树根为起点对每条边定向,就可以将它们变为通常的树。

连通图而言,若子图满足两个条件:① 包含原图所有n个顶点;② 仅用n-1条边连接所有顶点且无回路,则该子图为原图的生成树

图转生成树的两种方法

基于图的两种遍历方式,可生成对应类型的生成树,考研填空题常考:

  • 深度优先搜索生成树:依赖栈结构实现遍历,遍历路径构成的树;

  • 广度优先搜索生成树:依赖队列结构实现遍历,遍历路径构成的树。

生成树的判定依据(考研简答题要点)

需同时满足:① 顶点数n与边数m满足m = n-1;② 无回路;③ 保持原图的连通性(所有顶点均被连接)。

(3)最小生成树的概念与意义

前置概念:网

权值的图称为(区别于无权图),权值可表示路径长度、运输成本等实际意义。

最小生成树的定义

在一般情况下,图中的每条边若给定了权,这时,我们所关心的不是生成树,而是生成树中边上权值之和。若生成树中每条边上权值之和达到最小,称为最小生成树。

实际应用场景

如全国城市间修路 / 架线时,实现所有城市连通且总成本 / 总路程最低,核心是求解最小生成树。

最小生成树的两种经典算法

考研核心考察,需区分两种算法的流程与适用场景:

  • 普里姆(Prim)算法:本节重点;
  • 克鲁斯卡尔(Kruskal)算法:6.4.3 节内容(后续学习)。

6.4.2 普里姆(Prim)算法

(1)算法核心思想

在图中任取一个顶点K作为开始点,令U={k},W=V-U,其中V为图中所有顶点集,然后找一个顶点在U中,另一个顶点在W中的边中最短的一条,找到后,将该边作为最小生成树的树边保存起来,并将该边顶点全部加入U集合中,并从W中删去这些顶点,然后重新调整U中顶点到W中顶点的距离, 使之保持最小,再重复此过程,直到W为空集止。

(2)三个核心集合的定义(必须熟记)

  1. 顶点全集 V:图中所有顶点的集合,全程不变化;
  2. 已选顶点集 U:初始时仅包含任选的一个起点顶点,后续逐步加入新顶点;
  3. 未选顶点集 W:W = V - U,随 U 的扩充逐步缩小,最终为空集时算法终止。

(3)算法执行流程(考研核心,需掌握步骤逻辑)

1.初始化阶段

  • 从 V 中任选一个起点顶点(如顶点 1)加入 U,此时U={1},W={剩余所有顶点};
  • 虚线连接 U 中顶点与 W 中所有顶点,标注对应权值(无直接边的顶点间权值记为无穷大)。

2.迭代选边与扩充集合阶段

  • 第一步:寻找 U 与 W 之间权值最小的边,将其由虚线改为实线(作为最小生成树的边保留);
  • 第二步:将该最小边的 W 侧顶点并入 U,同时 W 中移除该顶点(U 扩充,W 缩小);
  • 第三步:修正权值(关键步骤):因 U 中新增顶点,需重新计算 W 中各顶点到 U 的权值(取与 U 中所有顶点边权的最小值,保留最小权值的边,删除原较大权值的边)。

3.终止条件

当U = V(即 W 为空集)时,算法终止,此时所有实线边构成最小生成树。

(4)算法实操案例(以 6 个顶点的无向连通网为例,起点为顶点 1)

  1. 初始化:U={1},W={2,3,4,5,6},1 与 2/3/4 的权值为 6/1/5,与 5/6 的权值为无穷大,均为虚线连接;

  2. 第一次迭代

    • 最小权边为 1-3(权值 1),实线保留,U={1,3},W={2,4,5,6};
    • 修正权值:3 与 2/5/6 的权值为 5/5/4,对比原 1-2(6)、1-5(无穷)、1-6(无穷),更新 2 的权为 5、5 的权为 5、6 的权为 4,1-4 的权 5(3-4 权 7,保留原权);

  3. 第二次迭代

    • 最小权边为 3-6(权值 4),实线保留,U={1,3,6},W={2,4,5};
    • 修正权值:6 与 4 的权为 2(小于原 1-4 的 5),更新 4 的权为 2;6 与 2/5 的权为无穷 / 6,保留原权;

  4. 第三次迭代

    • 最小权边为 6-4(权值 2),实线保留,U={1,3,6,4},W={2,5};

    • 修正权值:4 与 2/5 无直接边(权为无穷),保留原权(2 的权 5、5 的权 5);

  5. 第四次迭代

    • 最小权边为 3-2(权值 5),实线保留,U={1,3,6,4,2},W={5};
    • 修正权值:2 与 5 的权为 3(小于原 3-5 的 5),更新 5 的权为 3;

  6. 第五次迭代

    • 最小权边为 2-5(权值 3),实线保留,U={1,3,6,4,2,5},W=∅,算法终止。

(5)算法规律与考研注意要点

步骤数量规律

若图有n个顶点,除原图外需绘制n个阶段图(含 1 次初始化 +n-1次迭代),最终生成n-1条实线边(符合生成树边数要求)。

考研答题技巧

若要求书写算法,可采用伪代码形式(无需严格 C/Java 语法),核心是体现 “集合划分 - 选最小边 - 扩充集合 - 修正权值” 的逻辑,即可获得高分。

算法适用场景(扩展,衔接后续克鲁斯卡尔算法)

普里姆算法按顶点为单位处理,时间复杂度主要与顶点数相关,适合稠密图(顶点少、边多的图);后续克鲁斯卡尔算法按为单位处理,适合稀疏图(顶点多、边少的图)。

6.4.3 克鲁斯卡尔(kruskal)算法

(1)算法基本思想

  • 克鲁斯卡尔算法的基本思想是:将图中所有边按权值递增顺序排列,依次选定取权值较小的边,但要求后面选取的边不能与前面选取的边构成回路,若构成回路,则放弃该条边,再去选后面权值较大的边,n个顶点的图中,选够n-1条边即可。

  • 讲课通俗解释:“先把所有边按权值从小到大排好队,每次挑最小的边,只要这条边不跟之前选的边连成圈(回路)就留下,等凑够n-1条边就停 —— 因为每次都选最小的边,最后所有边的权值加起来肯定是最小的”。

例如,对图 中无向网,用克鲁斯卡尔算法求最小生成树的过程见图。

(2)核心步骤

“6 个顶点(1~6)无向网” 为例,边权关系(按讲课描述推导):1-3(权最小为1)、4-6(次小为2)、2-5(权较小为3)、3-6(权为4)、1-4(权为5)、2-3(权为5)、3-5(权较小为5)、1-2(权为6)、5-6(权为6)、3-7(权为7),步骤如下:

步骤 操作内容 已选边数 是否构环 结果(保留 / 丢弃)
1 对所有边按权值递增排序:1-3 < 4-6 < 2-5 < 3-6 < ... - - 排序完成
2 选权最小的边 1-3 1 无环 保留
3 选次小边 4-6 2 无环 保留
4 选边 2-5 3 无环 保留
5 尝试选边 3-6 4 无环 丢弃
6 选边 1-4:与已选边 1-3、3-6 、4-1构成回路(1-3-6-4) - 有环 保留
7 选边 2-3(或3-5,满足n-1=5条) 5 无环 保留
8 终止:已选 5 条边(6-1=5),构成最小生成树 5 - 算法结束

(3)关键注意点(课件结论 + 讲课强调)

  • 适用场景:仅针对无向网(最小生成树的本质是 “连接所有顶点且无回路”,有向图不存在生成树概念),与 6.4.2 普里姆算法一致。

  • 与普里姆算法的核心区别:

    对比维度 克鲁斯卡尔算法 普里姆算法
    出发角度 从 “边” 出发(按权选边) 从 “顶点” 出发(扩充顶点集)
    适用图类型 稀疏图更优(边少,排序效率高) 稠密图更优(顶点少,更新快)
    核心逻辑 避环(选边不构环) 扩点(逐步加顶点,更短距离)
  • 回路判断方法(课件未详述,补充扩展):

    实际实现中需用并查集(DSU,Disjoint Set Union) :

    1. 初始化每个顶点为独立集合(根节点为自身);
    2. 选边(u,v)时,查找uv的根节点:
      • 若根节点相同:uv已在同一集合,选边必构环,丢弃;
      • 若根节点不同:合并两个集合,保留边(u,v)

(4)实例结果(讲课案例总结)

  • 顶点数n=6,最终选取 5 条边:1-3、4-6、2-5、3-6、2-3或者3-5;
  • 所有保留边的权值之和最小,且无回路,构成 “克鲁斯卡尔最小生成树”。

6.4.4.单源点最短路径

(1)最短路径

  交通网络中常常提出这样的问题:从甲地到乙地之间是否有公路连通?在有多条通路的情况下,哪一条路最短? 交通网络可用带权图来表示。顶点表示城市名称,边表示两个城市有路连通,边上权值可表示两城市之间的距离、交通费或途中所花费的时间等。求两个顶点之间的最短路径,不是指路径上边数之和最少,而是指路径上各边的权值之和最小。
  另外,若两个顶点之间没有边,则认为两个顶点无通路,但有可能有间接通路(从其它顶点达到)。路径上的开始顶点(出发点)称为源点,路径上的最后一个顶点称为终点,并假定讨论的权值不能为负数。

(2)基础概念与算法思想

①单源点最短路径定义:

给定一个出发点(单源点)和一个有向网G=(V,E),求出源点到其它各顶点之间的最短路径。
通俗比喻:“把源点当成北京,其他顶点当成各个城市,求从北京到每个城市的‘总花费最少’的路线(花费对应权值,可能是距离、时间、钱),哪怕中转几次,只要总花费比直达少就选中转路线”。

  那么怎样求出单源点的最短路径呢?我们可以将源点到终点的所有路径都列出来,然后在里面选最短的一条即可。但是这样做,用手工方式可以,当路径特别多时,显得特别麻烦,并且没有什么规律,不能用计算机算法实现。
  迪杰斯特拉(Dijkstra)在做了大量观察后,首先提出了按路长度递增序产生各顶点的最短路径算法,我们称之为迪杰斯特拉算法。

②算法思想:

  算法的基本思想是:设置并逐步扩充一个集合S,存放已求出其最短路径的顶点,则尚未确定最短路径的顶点集合是V-S,其中V为网中所有顶点集合。按最短路径长度递增的顺序逐个以V-S中的顶点加到S中,直到S中包含全部顶点,而V-S为空。

分集合:S(已确定最短路径的顶点)、W=V-S(未确定的顶点);
初始态:S仅含源点,W含其他所有顶点,记录源点到各顶点的初始距离(直达边为权值,无直达边为∞(无穷大));
迭代过程:每次从W中选 “源点到该顶点距离最小” 的顶点,加入S;然后通过该新顶点 “中转”,修正W中其他顶点的距离(若中转距离比原距离短,则更新);
终止:S包含所有顶点(W为空),此时记录的距离即为源点到各顶点的最短路径长度。

具体做法是:设源点为Vl,则S中只包含顶点Vl,令W=V-S,则W中包含除Vl外图中所有顶点,Vl对应的距离值为0,W中顶点对应的距离值是这样规定的:若图中有弧<Vl,Vj>则Vj顶点的距离为此弧权值,否则为∞(一个很大的数),然后每次从W中的顶点中选一个其距离值为最小的顶点Vm加入到S中,每往S中加入一个顶点Vm,就要对W中的各个顶点的距离值进行一次修改。若加进Vm做中间顶点,使<Vl,Vm>+<Vm,Vj>的值小于<Vl,Vj>值,则用<Vl,Vm>+<Vm,Vj>代替原来Vj的距离,修改后再在W中选距离值最小的顶点加入到S中,如此进行下去,直到S中包含了图中所有顶点为止

(2)核心步骤(课件流程 + 讲课详细实例)

讲课实例:有向网含 5 个顶点(1~5),源点为 1,边权关系(按讲课描述整理):

  • 直达边:1→2(权 3)、1→5(权 30);1→3(无,)、1→4(无,);
  • 中转边:2→3(权 25)、2→4(权 8)、4→3(权 4)、4→5(权 12)、3→5(权 10)。
步骤 1:初始化
  • 集合:S={1}(源点),W={2,3,4,5}

  • 距离数组(dist[顶点],表示源点 1 到该顶点的距离):

    dist[1]=0(源点到自身),dist[2]=3dist[3]=∞dist[4]=∞dist[5]=30

步骤 2:第一次选顶点 + 修正距离
  • Wdist最小的顶点:2(dist=3),加入SS={1,2}
  • 修正W(3,4,5)的距离(通过顶点 2 中转):
    • dist[3]:原 → 1→2→3(3+25=28),更新为 28;
    • dist[4]:原 → 1→2→4(3+8=11),更新为 11;
    • dist[5]:1→2→5(无此边,),比原 30 大,不更新;
  • 此时dist[0,3,28,11,30](索引 1~5)。
步骤 3:第二次选顶点 + 修正距离
  • Wdist最小的顶点:4(dist=11),加入SS={1,2,4}
  • 修正W(3,5)的距离(通过顶点 4 中转):
    • dist[3]:原 28 → 1→2→4→3(3+8+4=15),更新为 15;
    • dist[5]:原 30 → 1→2→4→5(3+8+12=23),更新为 23;
  • 此时dist[0,3,15,11,23]
步骤 4:第三次选顶点 + 修正距离
  • Wdist最小的顶点:3(dist=15),加入SS={1,2,4,3}
  • 修正W(5)的距离(通过顶点 3 中转):
    • dist[5]:原 23 → 1→2→4→3→5(3+8+4+10=25),比 23 大,不更新;
  • 此时dist[0,3,15,11,23]
步骤 5:第四次选顶点 + 终止
  • W中仅剩的顶点:5(dist=23),加入SS={1,2,4,3,5}(包含所有顶点);

  • 终止,最终最短距离:

    1→2(3)、1→4(11)、1→3(15)、1→5(23)。

(3)关键注意点(课件结论 + 讲课强调)

  • 适用场景:仅针对有向网,且边权必须为非负数(若含负权边,会导致已加入S的顶点距离被后续负权路径更新,破坏算法逻辑,后续需学贝尔曼 - 福特算法解决)。

  • 与最小生成树的核心区别(讲课重点):

    对比维度 单源点最短路径(迪杰斯特拉) 最小生成树(克鲁斯卡尔 / 普里姆)
    图类型 有向网 无向网
    核心目标 源点到每个顶点的 “路径权和最小” 连接所有顶点的 “边权总和最小”
    路径特性 有方向(如 1→2 存在,2→1 可能不存在) 无方向(边是双向的)
    结果形式 距离数组(源点到各顶点的最短距离) n-1条边(构成树)
  • 终止条件S包含所有顶点(W为空),此时dist数组的每个值就是源点到对应顶点的最短路径长度。

(4)扩展说明(课件未详述,补充实用内容)

  • 松弛操作(Relaxation):算法中 “修正距离” 的本质的是松弛操作 —— 对边(u,v),若dist[v] > dist[u] + weight(u,v),则更新dist[v] = dist[u] + weight(u,v),即 “通过u中转比直达v更近,就更新v的距离”。

  • 算法优化(实际实现):

    手动计算时可直接选W中最小dist顶点,但代码实现中用优先队列(小根堆)

    优化该步骤,时间复杂度从O(n²)(稠密图)降至O(m log n)m为边数,稀疏图更优)。

  • 无法处理的场景:

    若有向网含负权边或负权回路(如边权为 - 2),迪杰斯特拉算法失效,需改用贝尔曼 - 福特算法(检测负权回路)或弗洛伊德算法(求所有顶点对的最短路径,可处理负权边但不能处理负权回路)。

(5)所有顶点对之间的最短路径

顶点对之间的最短路径概念

所有顶点对之间的最短路径是指:对于给定的有向网G=(V,E),要对G中任意一对顶点有序对V、W(V≠W),找出V到W的最短距离和W到V的最短距离。
解决此问题的一个有效方法是:轮流以每一个顶点为源点,重复执行迪杰斯特拉算法n次,即可求得每一对顶点之间的最短路径,总的时间复杂度为O(n3)。

6.4.5拓扑排序(Topological Sort)

(1)拓扑排序的核心概念

  • 定义:对 “顶点表示活动、边表示活动优先顺序” 的有向无环图(DAG),按 “先完成前驱活动,再开始后继活动” 的规则,输出所有顶点的有序序列,称为拓扑排序。

  • 生活 / 工程关联

    • 课程安排:先修课(前驱活动)完成后,才能学后续课(后继活动),如 “数据结构” 需先学 “程序设计基础(C2)” 和 “离散数学(C3)”。

    • 工程调度:大工程拆分为子活动(如 “采购材料”“搭建框架”),需按先后顺序执行,才能省时高效。

拓扑排序正是解决 “有优先关系的活动排序” 的核心算法。

(2)核心基础:AOV 网(Activity On Vertex Network)

  • AOV 网定义:Activity On Vertex Network(顶点表示活动的网),是拓扑排序的核心数据结构,满足两个特性:
    • 边的含义:若存在有向边 ` 活动 I 是活动 J 的直接前驱,J 是 I 的直接后继(I 必须先完成,J 才能开始)。
    • 无回路 + 反自反性:不存在 “活动 I 是自身前驱 / 后继” 的情况,也无有向环(否则会出现 “先做 A 才能做 B,先做 B 才能做 A” 的矛盾,无法排序)。

(3)拓扑排序的定义与步骤

  • 定义:对 AOV 网的顶点进行线性排序,使得对任意有向边 <i,j>,活动 i 在排序序列中均位于活动 j 之前(满足优先关系)。

  • 核心步骤(课件三步法,结合 “课程安排” 实例推演):

步骤 具体操作 关键说明
1 在 AOV 网中找到入度为 0的顶点(无前驱活动,可直接开始),将其输出 入度:指向该顶点的边的数量(前驱活动数量),入度为 0 表示无前置约束
2 从 AOV 网中删除该顶点,以及所有从该顶点出发的有向边(解除对后继活动的约束) 删除边后,需更新后继顶点的入度(如删除1,C8>,C8 的入度减 1)
3 重复步骤 1-2,直到两种情况之一: 所有顶点均输出(拓扑排序成功,AOV 网无环);② 无入度为 0 的顶点但仍有未输出顶点(排序失败,AOV 网有环) 核心功能:既输出活动顺序,又能判断 AOV 网是否有环(工程 / 课程安排中需避免环)

(4)实例解析(计算机专业课程安排)

  • 已知条件:部分课程及先修关系(如 C1 = 高等数学,无先修课;C4 = 数据结构,先修 C2、C3;C6 = 编译原理,先修 C5、C7),构建 AOV 网后执行拓扑排序:
    • 初始入度为 0 的顶点:C1(高数)、C2(程序设计基础)→ 输出 C1;
    • 删除 C1 及边 ``→ C3 入度 = 1(原 2),C8 入度 = 0 → 此时入度为 0 的顶点:C2、C8 → 输出 C2、C8;
    • 删除 C2(边2,C3>)、C8(无出边)→ C3 入度 = 0,C9 入度 = 0 → 输出 C3、C9;
    • 删除 C3(边3,C4>)、C9(无出边)→ C4 入度 = 0,C5 入度 = 0 → 输出 C4、C5;
    • 删除 C4(边4,C7>)、C5(边5,C7>)→ C7 入度 = 0 → 输出 C7;
    • 删除 C7(边7,C6>)→ C6 入度 = 0 → 输出 C6;
    • 结果:所有顶点输出(C1→C2→C8→C3→C9→C4→C5→C7→C6),排序成功,无环。

(5)扩展应用与注意事项

  • 应用场景:课程表生成、项目进度规划(如建筑工程 “打地基→砌墙→封顶”)、依赖包安装(如软件安装时先装依赖组件)。
  • 注意事项:拓扑排序结果不唯一(如步骤 2 中可先输出 C8 再输出 C2),只要满足前驱 - 后继关系即可;若 AOV 网有环,需先破除环(如修改课程先修关系)。

6.4.6 关键路径

(1)关键路径的核心问题

  • 解决两个核心需求(基于带权有向图):
    • 工程最短完成时间:从工程开始到结束,最少需要多少时间(如建筑工程最少 60 天);
    • 确定关键活动:若某活动延误,会导致整个工程延误,该活动称为 “关键活动”;关键活动连成的路径,称为 “关键路径”(需重点监控)。

(2)关键路径的基础 ——AOE 网

  • AOE 网定义:Activity On Edge Network(边表示活动、顶点表示事件的网),与 AOV 网的核心区别如下:
对比维度 AOV 网(拓扑排序用) AOE 网(关键路径用)
顶点含义 活动(如 “学数据结构”) 事件(如 “数据结构学完”“工程开工”)
边的含义 活动优先顺序(无权重) 活动 + 活动持续时间(有权重,如 “打地基需 10 天”)
核心特性 无环、反自反 有一个原点(起始事件,如 V1 = 工程开工)、一个汇点(结束事件,如 V9 = 工程竣工)
  • 事件的意义:顶点(事件)发生,意味着所有指向该顶点的活动(边)均已完成,且从该顶点出发的活动(边)可开始。例如:AOE 网中顶点 V5 发生,说明 “活动 A7(V3→V5)”“活动 A8(V4→V5)” 已完成,“活动 A9(V5→V7)” 可开始。

(3)关键路径的核心变量(5 个)

需先计算 4 个基础变量,再推导 1 个关键变量:

变量符号 含义 计算规则
Ve(i) 事件 i 的最早发生时间 从原点到顶点 i 的最长路径长度(因需等所有前驱活动完成,取最长时间)
Vl(i) 事件 i 的最晚发生时间 从顶点 i 到汇点的最长路径长度,再用 “汇点的 Ve值 - 该长度”(不延误总工期的前提下,事件 i 最晚可何时发生)
e(k) 活动 k 的最早开始时间 活动 k 对应边<i,j>,e(k)=Ve(i)(事件 i 最早发生时,活动 k 可开始)
l(k) 活动 k 的最晚开始时间 活动 k 对应边 `,l(k)=Vl(j)−活动k的权重(事件 j 最晚发生前,活动 k 需完成,故用 Vl(j) 减活动时长)
l(k)−e(k) 活动 k 的余量时间 若值为 0:活动 k 是关键活动(无余量,延误即影响工期);若值 > 0:非关键活动(有缓冲时间,可适当延误)

(4)关键路径计算步骤(实例解析)

以讲课中 “11 项活动、9 个事件” 的 AOE 网为例(原点 V1,汇点 V9,边权重为活动时长,如 A1=V1→V2,时长 6 天),计算步骤如下:

步骤 1:计算所有事件的最早发生时间Ve(i)(从原点 V1开始,正向推导)
  • 原点规则:Ve(1)=0(起始事件最早 0 时刻发生);

  • 正向推导:对每个顶点 j,Ve(j)=max{Ve(i)+边的权重}(取所有前驱路径的最长值):

    • Ve(2)=Ve(1)+A1权重=0+6=6;

    • Ve(3)=Ve(1)+A2权重=0+4=4;

    • Ve(4)=Ve(2)+A3权重=6+5=11;

    • Ve(5)=max{Ve(2)+A4=6+3=9,Ve(3)+A*5=4+5=9}=9;

    • 以此类推,直到计算出汇点V**E(9)=20(即工程最短完成时间为 20 天)。

步骤 2:计算所有事件的最晚发生时间Vl(i)(从汇点 V9 开始,反向推导)
  • 汇点规则:Vl(9)=Ve(9)=20(结束事件最晚发生时间 = 最早发生时间,否则工期延误);

  • 反向推导:对每个顶点 i,Vl(i)=min{Vl(j)−边}(取所有后继路径的最小值,不影响总工期):

  • Vl(8)=Vl(9)−A11权重=20−2=18;

  • Vl(7)=Vl(8)−A10权重=18−4=14;

  • Vl(6)=min{Vl(7)−A8=14−3=11,Vl(8)−A9=18−5=13}=11;

  • 以此类推,直到计算出所有顶点的Vl(i)。

步骤 3:计算所有活动的e(k)、l(k)及余量时间
  • 对每个活动 k(对应边 `:

    • e(k)=Ve(i),l(k)=Vl(j)−边<i,j>权重;

    • 举例:活动 A1(V1→V2,权重 6):e(A1)=Vl(1)=0,l(A1)=Vl(2)−6=6−6=0,余量 = 0→关键活动;

    • 活动 A5(V3→V5,权重 5):e(A5)=Ve(3)=4,l(A5)=Vl(5)−5=9−5=4,余量 = 0→关键活动;

    • 活动 A6(V3→V6,权重 2):e(A6)=Ve(3)=4,l(A6)=Vl(6)−2=11−2=9,余量 = 5→非关键活动(可延误 5 天)。

步骤 4:确定关键路径
  • 关键活动(余量 = 0)连成的路径,即关键路径。本例中关键路径为:V1→V2→V5→V7→V8→V9(对应活动 A1→A4→A9→A10→A11),该路径总时长 = 20 天(与工程最短完成时间一致)。

(5)扩展应用与优化思路​

  • 应用场景:项目管理(如软件开发 “需求分析→编码→测试”)、生产调度(如汽车生产 “零件加工→组装→质检”);
  • 优化思路:缩短关键路径的总时长,可通过压缩关键活动的时长(如 “打地基从 10 天压缩到 8 天”);非关键活动的余量时间可用于资源调配(如将非关键活动的工人调去支援关键活动)。
  • 注意事项:若关键路径有多条(如工程有 2 条关键路径,总时长均 20 天),需同时监控所有关键路径,任一关键路径延误都会导致工期延误。

参考资料:教材《数据结构 C 语言 第 3 版》 数据结构考研指导(基础篇) 、数据结构考研指导(基础篇) 视频课程|赵海英

posted @ 2025-12-16 15:51  CodeMagicianT  阅读(12)  评论(0)    收藏  举报