“图”学习提纲
前言
“图”学习提纲。
代码模板
图的基本概念
依据相关定义:
- 图:点,边
注意:图不可以为空图。即必须有点(有穷非空),不必须有边
- 无向图,有向图:无向边,有向边/弧:弧尾,弧头
- 简单图,多重图
数据结构中一般讨论简单图:不存在顶点到自身的边,不存在重复的边
- 完全图:无向完全图,有向完全图
无向完全图:边数为n(n-1)/2。n为点数
有向完全图:边数为n(n-1)。n为点数
- 稀疏图,稠密图
稀疏和稠密是相对而言的模糊概念
带权图/网:权
子图:生成子图
依据点与边的关系:
- 点的度,入度,出度
对无向图:点的度的和=边数×2
对有向图:点的入度的和=点的出度的和=边数;度=入度+出度
有向树
路径,路径长度,回路/环:简单路径,简单回路/环,最短路径/距离
若图有n个点,大于n-1条边,则存在回路
- 连通,连通图,非连通图,极大连通子图/连通分量
若图有n个点,小于n-1条边,则为非连通图
- 强连通,强连通图,强非连通图,极大强连通子图/强连通分量
注意:对无向图,为连通性;对有向图,为强连通性
- 极小连通子图/生成树,生成森林
图的生成树有n-1条边,有n-1条边不一定是图的生成树。n为点数
图的存储结构
邻接矩阵
类型:顺序存储结构
组成:
- 点数
- 边数
- 点一维数组:存储点数据
- 边二维数组:存储数值,可存储权值
创建的时间复杂度:
- O(v²)。v为点数
- O(v+v²+e) 。v为点数,e为边数。v为初始化点一维数组,v²为初始化边二维数组,e为给边赋值
空间复杂度:
- O(v²)。v为点数
- O(v+v²)。v为点数。v为点一维数组,v²为边二维数组
适用:稠密图
相关性质:
- 无向图:对称(可压缩存储上/下三角元素);有效数值(如“1”表示点间存在边)数为边数的两倍;第i行或第i列的元素和为点i的度
- 有向图:不对称;有效数值数为边数;第i行的元素和为点i的出度,第i列的元素和为点i的入度
- 求点间是否有边:O(1)(依据点数据/下标取值)
- 求点的邻边/邻接点:O(v)。v为点数(遍历点数据所在的一行)
- 有权图:点到点有路径,数值为权值;点到当前点无路径,数值为0;点到其他点无路径,数值为无穷大
- 存在邻接矩阵A,A的n次方的元素:A的n次方[i][j]=点i到点j中,长度为n的路径数
- 邻接矩阵唯一
邻接表
类型:顺序和链式存储结构;关注点
组成:
- 点数
- 边数
- 点表:顺序存储结构;一维数组;单链表的头结点:存储点数据(当前点)和当前点指向第一个边结点的指针
- (出)边表:链式存储结构;单链表;单链表的非头结点:存储点数据(其他邻接点)和当前点指向其他边结点的指针,可存储权值
创建的时间复杂度:
- O(v+e) 。v为点数,e为边数。v为初始化点表,e为初始化边表
空间复杂度:
- 无向图:O(v+2e)。v为点数,e为边数
- 有向图:O(v+e)。v为点数,e为边数
适用:稀疏图
相关性质:
- 求点的出度:遍历该点的链表
- 求点的入度:遍历所有点的链表;使用逆邻接表
- 求点间是否有边:O(v)。v为其他邻接点数。(遍历该点的链表)
- 求点的邻边/邻接点:O(v)。v为其他邻接点数。(遍历该点的链表)
- 邻接表不唯一
邻接多重表
类型:无向图的顺序和链式存储结构;关注边
组成:
- 点表:顺序存储结构;单链表的头结点;存储点数据(当前点)和当前点指向第一个边结点的指针
- 边表:链式存储结构;单链表的非头结点;存储标志(可标记该边是否被搜索过),点i数据,点i(当前点)指向其他边结点的指针,点j数据(其他点),点j指向其他边结点的指针(“多重”的体现),可存储权值
空间复杂度:
- 无向图:O(v+2e)。v为点数,e为边数
相关性质:
- 易插入或删除边(相比于邻接表操作多个边结点,只需操作一个边结点)
十字链表
类型:有向图的顺序和链式存储结构
组成:
- 点表:顺序存储结构;单链表的头结点;存储点数据(当前点),以当前点为弧头的第一个边结点的指针,以当前点为弧尾的第一个边结点的指针
- 边表:链式存储结构;单链表的非头结点;弧尾点(当前点)数据,弧头点(其他点)数据,弧尾点(当前点)指向弧头相同的边结点的指针,弧头点(其他点)指向弧尾相同的边结点的指针,可存储权值
创建的时间复杂度:
- O(v+e) 。v为点数,e为边数。v为初始化点表,e为初始化边表
空间复杂度:
- 有向图:O(v+e)。v为点数,e为边数
相关性质:
- 易求点的入度和出度
- 图的十字链表不唯一,十字链表唯一确定图
边集数组
类型:顺序存储结构;关注边
组成:
- 点一维数组:存储点数据
- 边一维数组:存储点(起点)数据,存储点(终点)数据,可存储权值
相关性质:
- 求点的度:遍历边一维数组
- 适合依次操作边,不适合操作点
图的遍历算法
深度优先遍历(DFS)概述
- 类似二叉树先序遍历
- 是递归过程
- 图的邻接矩阵唯一,广度优先遍历序列唯一;图的邻接表不唯一,广度优先遍历序列不唯
- 保留遍历的边,删除未遍历的边,可形成深度优先遍历生成树(对连通图)或深度优先遍历生成森林(对非连通图)
深度优先遍历(DFS)的过程
- 初始化标记数组(空间复杂度:O(v)。v为点数)
- 循环遍历点表,标记每点为未访问
- 循环遍历点表,若点的标记为未访问,则深度优先遍历图g,从点v开始(连通图执行一次,非连通图执行多次。非连通无向图执行连通分量数次,非强连通有向图的非强连通分量不一定能遍历所有点)
- 深度优先遍历g,从v开始
- 访问v(二叉树先序遍历的体现)
- 标记v已访问
- 循环获取v的邻接点w
- 循环中,若w的标记为未访问,则深度优先遍历g,从w开始(递归的体现)
时间复杂度:依据存储结构
- 对邻接矩阵:遍历点数×查找每点的邻接点的时间=O(v×v)=O(v²)。v为点数(理解:遍历每一行)
- 对邻接表:遍历点数+查找每点的邻接点的时间=O(v+e)。v为点数,e为边数(理解:遍历点表和边表,因为是链式存储结构)
空间复杂度:O(v)。v为点数/辅助空间规模/递归栈规模
伪代码:
1 | bool visited[MAX_VERTEX_NUM]; //访问标记数组 |
广度优先遍历(BFS)概述
- 类似二叉树层序遍历
- 是迭代过程
- 图的邻接矩阵唯一,广度优先遍历序列唯一;图的邻接表不唯一,广度优先遍历序列不唯一
- 保留遍历的边,删除未遍历的边,可形成广度优先遍历生成树(对连通图)或广度优先遍历生成森林(对非连通图)
- 类似迪杰斯特拉(Dijkstra)单源最短路径算法
- 类似普里姆(Prim)最小生成树算法
广度优先遍历(BFS)的过程
- 初始化标记数组(空间复杂度:O(v)。v为点数)
- 循环遍历点表,标记每点为未访问
- 初始化队列(空间复杂度:O(v)。v为点数)
- 循环遍历点表,若点的标记为未访问,则广度优先遍历图g,从点v开始(连通图执行一次,非连通图执行多次。非连通无向图执行连通分量数次,非强连通有向图的非强连通分量不一定能遍历所有点)
- 广度优先遍历g,从v开始
- 访问v
- 标记v已访问
- v入队
- 当队列不为空时,循环
- 循环中,获取v的所有邻接点w(迭代的体现)
- 循环中,若w的标记为未访问,则访问w,标记w已访问,w入队(二叉树层序遍历的体现)
时间复杂度:依据存储结构
- 对邻接矩阵:遍历点数×查找每点的邻接点的时间=O(v×v)=O(v²)。v为点数(理解:遍历每一行)
- 对邻接表:遍历点数+查找每点的邻接点的时间=O(v+e)。v为点数,e为边数(理解:遍历点表和边表,因为是链式存储结构)
空间复杂度:O(v)。v为点数/辅助空间规模
伪代码:
1 | bool visited[MAX_VERTEX_NUM]; //访问标记数组 |
图的应用
最小(代价)生成树概述
概念:
- 对连通图
- 是极小连通子图
- 有图的所有点,尽量少的边
- 若少一条边,则成非连通图;若多一条边,则存在回路
性质:
- 边数=点数-1
- 树形不唯一。若图中各边权值不相等,则唯一;若图中存在相等权值的边,构造时都被加入树中,则唯一
- 边权值的和唯一且最小
构造算法:基于贪心思想;对无向图
- 普里姆(Prim)算法
- 克鲁斯卡尔(Kruskal)算法
构造算法的伪代码:
1 | GENERIC MST(G){ |
最小(代价)生成树的普里姆(Prim)算法
概述:
- 类似图的最短路径的迪杰斯特拉(Dijkstra)算法
- 对点
过程:选边加点加边
- 加点
- 加候选边
- 依据代价,从候选边中选边(每次构造一定为连通图)
- 依据所选边,加点
- 依据所加点,加候选边
- 重复345步,直到加入所有点(执行n-1次,n为点数)
伪代码:
1 | void Prim(G,T){ |
时间复杂度:O(n²)。n为点数
空间复杂度:O(n)。n为点数。辅助空间记录标记已访问点和候选边
适用:边稠密图
最小(代价)生成树的克鲁斯卡尔(Kruskal)算法
概述:
- 对边
过程:加边加点;合并点/连通分量/树
- 依据代价,排序边
- 依据代价,从小到大遍历边
- 若边为候选边,则加边(每次构造不一定为连通图)
- 依据所加边,若点为候选点,则加点,否则不加边不加点并转2
- 重复234步,直到直到加入n-1条边。n为点数/所有点在一个连通分量
若边为候选边和若点为候选点的另一种描述:若加边不构成回路,则加边加点,否则不加边不加点并转2
伪代码:
1 | void Kruskal(V,T){ |
时间复杂度:排序算法中常用堆排序,堆存储边:O(eloge)。e为边数
空间复杂度:O(n)。n为点数。辅助空间并查集记录点的树型结构
适用:边稀疏点稠密图/稀疏图
最短路径概述
概念:
- 对带权图
- 带权路径长度最短的路径
算法:
- 迪杰斯特拉(Dijkstra)算法
- 弗洛伊德(Floyd)算法
最短路径的迪杰斯特拉(Dijkstra)算法
概述:
- 类似图的最小(代价)生成树的普里姆(Prim)算法
- 对点
- 基于贪心策略
思想:求单源最短路径:图中某点到其他点的最短路径
求多源最短路径:图中多点到其他点的最短路径。时间复杂度:O(n×n²)=O(n的3次方)。n为点数
过程:选边加点加边
- 加点
- 加候选边
- 依据代价,从候选边中选边(每次构造一定为连通图)
- 依据所选边,加点
- 依据所加点,加候选边,更新代价
- 重复345步,直到加入所有点(执行n-1次,n为点数)
伪代码:
1 | void Dijkstra(MGraph g,int v,int dist[],int path[]) |
时间复杂度:O(n²)。n为点数
空间复杂度:O(n)。n为点数。辅助空间记录点间代价,路径中点的前一点和标记已访问点
适用:非带权图;不存在负权值的带权图
最短路径的弗洛伊德(Floyd)算法
思想:求点间的最短路径
过程:迭代
- 初始化代价矩阵为邻接矩阵,路径矩阵
- 遍历点
- 对所遍历点,遍历点对
- 对遍历点对,所遍历点作为中间点,依据代价,更新代价矩阵和路径矩阵
- 重复234步,直到遍历所有点(执行n-1次,n为点数)
伪代码:
1 | void Floyd(MGraph *g,int Path[][maxSize],int A[][maxSize]) |
时间复杂度:O(n的3次方)。n为点数
空间复杂度:O(n²)。辅助空间记录代价和路径
适用:非带权图;可存在负权值,负权值边不成回路的带权图
拓扑排序概述
顶点表示活动的网/活动在顶点上的网(AOV网):
- 工程中,各活动间先后关系的有向无环图
- 无回路的有向图/有向无环图/DAG图表示工程,点表示活动,边表示活动的先后关系
有向无环图可类比树,使用表达式描述
算法:拓扑排序
拓扑排序算法
概念:
- 对有向无环图,构造拓扑排序序列的过程
- 每点访问且仅访问一次
- 若图中存在点A到点B的路径,则拓扑排序序列中点A在点B的前面
过程:
- 选择一个没有前驱/入度为0的点,输出
- 删除该点和以该点为起点的有向边
- 重复12步,直到AOV网为空(排序成功)或AOV网不为空且不存在没有前驱/入度为0的点(存在回路,排序失败)
伪代码:
1 | int TopSort(AGraph *G) |
时间复杂度:依据存储结构
- 对邻接矩阵:O(n²)。n为点数(理解:遍历每一行)
- 对邻接表:O(n+e)。n为点数,e为边数(理解:遍历点表和边表,因为是链式存储结构)
空间复杂度:O(n)。n为点数。辅助空间栈记录前驱/入度为0点
其他:
- 拓扑排序序列不唯一。因为可能同时存在多个入度为0的点,选择不同,序列不同
- 因为点地位平等,由人工编号,所以可依据拓扑排序序列对点重新编号,生成新邻接矩阵,该邻接矩阵可为三角矩阵
- 对一般图,若邻接矩阵是三角矩阵,则存在拓扑排序序列,反之不一定
- 拓扑排序:对入度
- 逆拓扑排序:对出度
- 若有向图无回路,则可用深度优先遍历进行拓扑排序,深度优先遍历序列为拓扑排序序列
- 判断有向图有回路的方法:深度优先遍历;拓扑排序
关键路径概述
边表示活动的网/活动在边上的网(AOE网):
- 工程中,各活动持续时间的有向无环图
- 无回路的有向图/有向无环图/DAG图表示工程,边表示活动,边的权值表示活动的持续时间;点表示事件(新活动开始或旧活动结束的标志)
注意:AOV网的边无权值
AOE网的性质:
- 只有点表示的事件发生后,以该点为起点的边表示的活动才能开始
- 只有所有以该点为终点的边表示的活动结束后,该点表示的事件才能发生
相关概念:
- 源点:入度为0的点,只有一个
- 汇点,出度为0的点,只有一个
- 关键路径:从源点到汇点的所有路径中,有最长/大路径长度的,最短完成工程时间的路径(重点理解)
- 最长/大路径长度理解:只有所有最久持续时间的活动结束,工程才结束
- 最短完成工程时间理解:要想工程结束,最少/必须经过的时间
- 关键活动:关键路径上的活动
注意:
- 关键活动决定工程。可缩短关键活动的持续时间,以缩短工程的工期;可延长关键活动的持续时间,以延长工程的工期
- 不能任意缩短关键活动的持续时间。若缩短到一定程度,则关键活动可能成为非关键活动
- 关键路径可能不唯一
- 若AOE网存在多条关键路径,缩短一条关键路径上关键活动的持续时间不一定能缩短工程的工期(因为关键活动可能成为非关键活动,关键路径可能成为非关键路径);缩短所有关键路径上共有的关键活动能缩短工程的工期
- 无论AOE网存在一条或多条关键路径,延长任一条关键路径上任一个关键活动的持续时间能延长工程的工期
算法:求关键路径
关键路径算法
算法的参量(重点理解):
- 事件的最早发生时间:源点到点的最长路径长度(理解:只有最久的准备事件结束,才能开始当前事件)
- 事件的最晚发生时间:汇点到点的最短路径长度(理解:要想准时开始当前事件,只有最早开始准备事件)
- 活动的最早发生时间:边起点表示事件的最早发生时间(理解:只要事件开始,活动就开始)
- 活动的最晚发生时间:边终点表示事件的最晚发生时间-该边表示活动的持续时间(理解:事件最晚开始时间,减活动持续时间,得活动最晚开始时间)
- 活动的剩余时间:活动的最晚发生时间-活动的最早发生时间(反映活动完成的松紧度)
- 关键活动:活动的剩余时间=0
- 关键路径:关键活动构成的路径
过程:
- 拓扑排序
- 依据拓扑排序序列,顺序求各点表示事件的最早发生时间(从前往后;源点表示事件的最早发生时间等于0)
- 逆拓扑排序
- 依据逆拓扑排序序列,顺序求各点表示事件的最晚发生时间(从后往前;汇点表示事件的最晚发生时间等于事件的最早发生时间)
- 求各点/边表示活动的最早发生时间
- 求各点/边表示活动的最晚发生时间
- 求活动的剩余时间/关键活动
- 求关键路径
总结
“图”学习提纲。
参考资料
- 《2023版数据结构高分笔记》主编:率辉
- 《2023年计算机组成原理考研复习指导》组编:王道论坛
- 《大话数据结构》作者:程杰
作者的话
- 感谢参考资料的作者/博主
- 作者:夜悊
- 版权所有,转载请注明出处,谢谢~
- 如果文章对你有帮助,请点个赞或加个粉丝吧,你的支持就是作者的动力~
- 文章在描述时有疑惑的地方,请留言,定会一一耐心讨论、解答
- 文章在认识上有错误的地方, 敬请批评指正
- 望读者们都能有所收获