1.图的定义和基本术语
- 图的定义
- 图的基本术语
2.图的存储结构
- 邻接矩阵
- 邻接表
- 十字链表
- 邻接多重表
3.图的遍历
- 深度优先搜索
- 广度优先搜索
4.图的应用
- 最小生成树
- 最短路径
- 拓扑排序
- 关键路径
1.1图的定义
图的结构是由顶点和边构成的
规定:图G由两个集合V,E组成,记为G=(V,E)
V:是顶点的集合,E:是边的集合
图:有向图
,无向图
有向图
:
两个顶点之间的边有方向,对应的图为有向图
(对<A,B>而言,A是有向边的始点,B是有向边的终点,<A,B>和<B,A>是两条不同的边)
无向图
:
两个顶点之间的边有方向,对应的图为无向图
(对(A,B)而言,没有方向而言,只是表明是顶点A,B之间的边,(A,B)和(B,A)是同一条边)
1.2图的基本术语
子图:就是在图1的基础上少了一些顶点和边,G1包含G2
(注意:G2顶点的边必须和G1一致)
无向完全图和有向完全图:无向完全图就是每一个顶点都需要连接上其他顶点,有向完全图就是每个顶点都需要连接上其他顶点,但由于弧是有向的,所以需要反向在连接一遍
(注意:有向边也称弧,箭头端称弧头,箭尾端弧尾)
稀疏图和稠密图:有很少边(弧)称为稀疏图,反之称稠密图
权和网:在实际行动中,每条边可以标上具有特殊含义的数值,称该数值是该边的权值,这种带权值的图称为网
邻接点:就是两个顶点之间有边相连
度,入度,出度:顶点的度就是指与该顶点相关联的边的数目(邻接点),入度和出度是对有向图而言的,顶点入度就是指弧头指向该顶点,顶点出度就是指弧尾指向该顶点
路径和路径长度:路径指的是一个顶点v到一个顶点v1之间经过的一系列顶点的序列,路径长度指的是一条路径上经过的边或者弧的数目
(注意:如果G是有向的,则路径也是有向的)
回路或环:第一个顶点和最后一个顶点相同的路径称为回路或环
简单路径,简单回路或简单环:序列中顶点不重复出现的路径称为简单路径,将第一个顶点和最后一个顶点相连则称简单回路或简单环
连通,连通图和连通分量 :在无向图中,如果顶点v1到顶点v2有路径,则称两个顶点是连通的,如果在图中任意两个顶点都相连则称这个该图为连通图,连通分量指的是无向图中的极大连通子图
强连通图和强连通分量:在有向图中,如果在图中任意两个顶点都相连则称这个该图为强连通图,连通分量指的是有向图中的极大连通子图
连通图的生成树:一个极小的连通子图,它含有图中的全部顶点,但只有足以构成一棵树的n-1条边
(注意:有n-1条边的图不一定是生成树)
有向树和生成森林:有一个顶点的入度为0,其余顶点的入度均为1的有向图称为有向树,若干个有向树则生成一个有向图的森林
2.1邻接矩阵
2.1.1邻接矩阵表示法
🔴:邻接矩阵是表示顶点之间相邻关系的矩阵
使用一个二维数组存储邻接矩阵,还需要一个一维数组来存储顶点信息
对于图:A[i] [j]=1/0,当等于1时表示存在(i,j)/<i,j>边,当等于0时表示不存在(i,j)/<i,j>边
对于网:A[i] [j]=权值/无穷大,规定:当等于权值时表示存在(i,j)/<i,j>边,当等于无穷大时表示不存在(i,j)/<i,j>边
(无穷大表示计算机运行的,大于所以边上的权值的数)
2.1.2采用邻接矩阵表示法创建无向网
- 输入总顶点数和总边数
- 依次输入点的信息并将其存入顶点表中
- 初始化邻接矩阵,使每个权值初始化为极大值
- 构造邻接矩阵,依次输入每条边依附的顶点和其权值,确定两个顶点在图中的位置之后,使相应边赋予相应边的权值,同时让关于主对角线的边赋予相同的权值
🔴:主对角线的值始终为0
2.1.3邻接矩阵表示法的优缺点
优点:
- 便于判断两个顶点之间是否有边,即根据A[i] [j]=0/1来判断
- 便于计算各个顶点的度(对于无向图,邻接矩阵第i行元素之和计算顶点的度,对于有向图,第i行元素之和就是顶点出度,第i列元素之和就是顶点i的入度)
缺点:
- 不便于增加和删除顶点
- 不便于统计边的数目,时间复杂度为O(n*n)
- 空间复杂度高,空间复杂度为O(n*n)这对稀疏图尤其浪费
2.2邻接表
2.2.1邻接表的表示法
邻接表是图的一种链式存储结构
每个顶点v建立一个单链表,把与v相邻接的顶点放在这个链表中
邻接表由表头节点表
,边表
组成
表头节点表
:由所有表头节点以顺序结构的形式存储,以便可以随机访问任一顶点的边链表
表头节点包括数据域和链域
边表
:由表示图中顶点间关系的2n个边链表组成
边链表包括邻接点域,数据域和链域
🔴 其中邻接点域指示与顶点v邻接的点在图中的位置(一般填入顺序结构的下标)
简单来说:就是一个顶点需要建立一个单链表,每个单链表里面有一个表头节点和多个边节点,而多个边节点组成一个边链表。
而全部顶点的表头节点加起来称表头节点表,全部顶点的边链表加起来称边链表
表头节点+边链表=邻接表
- 对于无向图而言:
表头节点存放:(下标),数据域,链域
边链表存放:(下标),链域
- 对于有向图而言:
表头节点存放:(下标),数据域,链域
边链表存放:(下标),链域
顶点v的边链表有几个节点仅代表顶点v有几个出度,无法确定入度
所以:建立逆邻接表(每个顶点v建立一个链接所有进入v的边的表)
链域指向入度的下标
- 对于网而言:
表头节点存放:(下标),数据域,链域
边链表存放:(下标),数据域,链域
2.2.2采用邻接表表示法创建无向图
- 输入总顶点数和总边数
- 依次输入点的信息存放在顶点表中,使每个表头节点的指针域初始化为NULL
- 创建邻接表,依次输入每条边依附的两个顶点,确定这两个顶点的序号i和j后,将此边节点分别插入到对应的两个表头节点
2.2.3邻接表表示法的优缺点
优点:
- 便于增加和删除顶点(仅需要生成一个边表节点并插入边链表头部即可)
- 便于统计边的数目,按顶点表顺序查找所有边表即可,时间复杂度:O(N+e),其中N为顶点数,e为边数
- 空间效率高,适合使用稀疏图
缺点:
- 不便于判断顶点之间是否有边,要判断(vi,vj)边,就需要查找第i个边表,最坏的情况时间复杂度为O(N)
- 不便于计算有向图各个顶点的度,因为使用邻接表只能求出度,使用逆连接表只能求入度
2.3十字链表
十字链表是对有向图的另一种链式存储结构(可以看成是邻接表和逆邻接表结合的链表)
十字链表=弧节点+顶点节点(每一条弧有一个节点,每个顶点也有一个节点)
对于弧节点:
尾域,头域,链域hlink,链域tlink,info域
- 尾域和头域分别指示弧尾和弧头这两个顶点在图中的位置
- 链域hlink和链域tlink分别指示指向弧头相同的下一条弧和指向弧尾相同的下一条弧
- info域用来存放该弧的相关信息
对于顶点节点:
数据域,firstin域,firstout域
- 数据域用来存储和顶点相关的信息
- firstin域和irsstout域分别用来指向该顶点为弧头或弧尾的第一个弧节点
先写弧节点,在根据入弧和出弧的关系将顶点节点和弧节点联系起来
🔴弧节点所在的链表非循环链表,表头节点(顶点节点)之间不是链接关系而是顺序存储关系
2.4邻接多重表
邻接多重表是对无向图的另一种链式存储结构
每一条边用一个节点表示,它有6个域组成
- mark为标志域,可用标记该条边是否被搜索过
- ivex和jvex为该边依附的两个顶点在图中的位置
- ilink指向下一条依附于顶点ivex的边
- jlink指向下一条依附于顶点jvex的边
- info为指向和边相关的各种信息的指针域
每一个顶点用一个节点表示,它有2个域组成
- 数据域用于存储和该顶点相关的信息
- firstedge指示第一条依附该顶点的边
3.1深度优先搜索
- 从图中某个顶点v出发,访问v
- 找到刚访问过的顶点的第一个未访问的邻接点,访问该节点,以该节点为新节点,重复此步骤直到刚访问过的节点没有未访问过的节点为止
- 返回前一个访问过的且仍有未被访问的邻接点的顶点,然后还是寻找它未被访问过的邻接点并访问
- 重复2.3步骤,直到图中全部顶点都被访问了
🔵与树的先序遍历类似
3.2广度优先搜索
从某个顶点v出发,访问v,使v入列
只要队列不为空,重复以下操作:
- 队头元素u出队
- 依次检查u的所有邻接点w,如果w存在且未被访问,则访问w且将w入队
🔵与树的层次遍历类似
🔴:深度优先搜索和广度优先搜索都需要建立一个数组,该数组来判断顶点是否已经被访问过
4.1最小生成树
对于网而言,让权值之和最小的路径,对于实际操作而言,让实际代价(时间,金钱)最小的路径称为最小生成树(注意:没有方向)
🔴生成树:所有顶点均由边连接在一起,但不存在回路的图
- 一个图可以有许多棵不同的生成树
- 生成树的顶点=图的顶点
- 生成树是图的极小连通子图
- 生成树中在加一条边必然形成回路,有n个顶点的连通图的生成树有n-1条边
两种算法构造最小生成树:普里姆算法(prim)和克鲁斯卡尔算法(kruskal)
1.普里姆算法(记录点)
1.假设U是已经落在生成树的顶点集合,V-U是未落到生成树上的顶点集合,TE是最小生成树中边的集合
2.寻找V-U中权值最小的边(u,v),将该边记录到TE中,并将对应的顶点记录到U中
3.递归,每次找到新的顶点,需要重新从头开始访问,直到U=V为止
(时间复杂度:O(n*n))
2.克鲁斯卡尔算法(记录边)
1.先令最小生成树初始状态为只有n个顶点而无边的非连通图(每个顶点自成一个连通分量)
2.加权最小的边(不能成环),重复直到每个顶点都相连
(时间复杂度:O(e))
4.2最短路径
在带权有向网中,选择权值之和最小的路径,在实际操作中,让实际代价(时间,金钱)最小的路径称最短路径。
习惯上称路径上的第一个顶点为源点,最后一个顶点为终点(有方向)
两种常见的最短路径的问题:一种是求从某个源点到其余各顶点的最短路径,另一种是求每一对顶点之间的最短路径
1.求从某个源点到其余各顶点的最短路径(即每个顶点都需要经过)
1.先从源点出发,寻找到各终点的自达路径(仅一条弧)
2.对那些没有直达路径的顶点,选择先经过其他顶点在到达该顶点,对比选择权值之和最小的路径
按照迪杰斯特拉算法,先求有v直达路径的顶点的路径,然后按照路径长度递增的次序依次得到v到其他顶点的路径
迪杰斯特拉算法:使用带权的邻接矩阵来表示有向网
需要一些辅助数组
- 一维数组s:记录从源点到终点是否已被确定了最短路径长度
- 一维数组path:记录从源点到终点的当前最短路径上的直接前驱顶点序号,其初值:如果有弧则记录该顶点,无弧则记录-1
- 一维数组d:记录从源点到终点的当前路径最短路径长度,其初值:如果有弧则记录权值,无弧则表示无穷大
2.求每一对顶点之间的最短路径(不需要每个顶点经过)
将vi到vj最短路径长度初始化,然后进行n次比较和更新
1.对逐个顶点进行试探
2.从vi到vj的所有存在的路径中选出一条最短的路径
弗洛伊德算法:使用带权的邻接矩阵来表示有向网
需要一些辅助数组
- 二维数组path:最短路径上顶点vj的前一顶点的序号
- 二维数组d:记录vi,vj之间的最短路径长度
4.3拓扑排序
🔵: 拓扑排序一开始需要先访问一个入度为0的顶点,然后必须先把以该顶点为弧尾的其他顶点先访问完,在以同样的规则访问其他的顶点
AOV—网:用顶点表示活动,用弧表示活动之间的优先顺序的有向图
在网中,若顶点vi到vj有一条有向路径,则vi是前驱,vj是后继,若是一条弧,则称vi是直接前驱,vj是直接后继
拓扑排序:
将AOV-网中所有顶点排成一个线性序列(通俗来说:就是活动A开始的前提条件是活动B已经结束)
拓扑排序的过程:
- 在有向图中选一个无前驱的顶点且输出它
- 从图中删除该顶点和所有以它为尾的弧
- 重复1.2,直到不存在无前驱的顶点
若发现输出的顶点数等于有向图的顶点数,则说明输出的序列是拓扑排序
🔴:一般来说:当顶点连接了多个其他顶点,按照序号顺序大小输出(一个AOV网的拓扑排序不唯一)
需要一些辅助数据结构:
1.一维数组:存放各顶点入度,没有前驱的顶点就是入度为0的顶点
2.栈s:暂存所有入度为0的顶点(可以避免重复查找数组)
3.一维数组:记录拓扑排序的顶点序号
4.4关键路径
⚫️: 图的关键路径是建立在图的拓扑序列之上进行的(即也需要经过全部顶点)
AOE-网:以边表示活动的网,其中,顶点代表事件,弧代表活动,权代表活动持续时间,通常AOE-网用来估算工程的完成时间
源点:入度为0的顶点
汇点:出度为0的顶点
带权路径长度:一条路径中各弧上的权值之和
关键路径:找出一条从源点到汇点的带权路径长度最长的路径
(无论其他路径怎么走,都需要等待关键路径走完,才能到达最后顶点,也就是说整个图的所有顶点访问完所需要的时间只由关键路径决定)
关键活动:关键路径上的活动称关键活动
- 事件vi最早发生的时间(多路径选最大值)
等于从源头到vi的最长路径长度
- 事件vi最迟发生的时间(多路径选最小值)
等于后继事件vk的关键路径的时间—活动<vi,vk>的持续时间
- 活动ai=<ai,aj>最早发生的时间(弧尾顶点最早开始时间)
等于事件vi最早发生的时间
- 活动ai=<ai,aj>最迟发生的时间(弧头顶点最迟时间)
等于事件vj的最迟发生的时间—活动ai持续的时间
关键路径算法:使用邻接表作为有向图的存储结构
需要一些辅助数据结构
1.一维数组:事件vi的最早发生时间
2.一维数组:事件vi的最迟发生时间
3.一维数组:记录拓扑序列的顶点序号