数据结构6-图

87 阅读15分钟

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采用邻接矩阵表示法创建无向网

  1. 输入总顶点数和总边数
  2. 依次输入点的信息并将其存入顶点表中
  3. 初始化邻接矩阵,使每个权值初始化为极大值
  4. 构造邻接矩阵,依次输入每条边依附的顶点和其权值,确定两个顶点在图中的位置之后,使相应边赋予相应边的权值,同时让关于主对角线的边赋予相同的权值

🔴:主对角线的值始终为0

2.1.3邻接矩阵表示法的优缺点

优点:

  1. 便于判断两个顶点之间是否有边,即根据A[i] [j]=0/1来判断
  2. 便于计算各个顶点的度(对于无向图,邻接矩阵第i行元素之和计算顶点的度,对于有向图,第i行元素之和就是顶点出度,第i列元素之和就是顶点i的入度)

缺点:

  1. 不便于增加和删除顶点
  2. 不便于统计边的数目,时间复杂度为O(n*n)
  3. 空间复杂度高,空间复杂度为O(n*n)这对稀疏图尤其浪费

2.2邻接表

2.2.1邻接表的表示法

邻接表是图的一种链式存储结构

每个顶点v建立一个单链表,把与v相邻接的顶点放在这个链表中

邻接表由表头节点表边表组成

表头节点表:由所有表头节点以顺序结构的形式存储,以便可以随机访问任一顶点的边链表

表头节点包括数据域和链域

边表:由表示图中顶点间关系的2n个边链表组成

边链表包括邻接点域,数据域和链域

🔴 其中邻接点域指示与顶点v邻接的点在图中的位置(一般填入顺序结构的下标)

简单来说:就是一个顶点需要建立一个单链表,每个单链表里面有一个表头节点和多个边节点,而多个边节点组成一个边链表。

而全部顶点的表头节点加起来称表头节点表,全部顶点的边链表加起来称边链表

表头节点+边链表=邻接表

  • 对于无向图而言:

表头节点存放:(下标),数据域,链域

边链表存放:(下标),链域

  • 对于有向图而言:

表头节点存放:(下标),数据域,链域

边链表存放:(下标),链域

顶点v的边链表有几个节点仅代表顶点v有几个出度,无法确定入度

所以:建立逆邻接表(每个顶点v建立一个链接所有进入v的边的表)

链域指向入度的下标

  • 对于网而言:

表头节点存放:(下标),数据域,链域

边链表存放:(下标),数据域,链域

2.2.2采用邻接表表示法创建无向图

  1. 输入总顶点数和总边数
  2. 依次输入点的信息存放在顶点表中,使每个表头节点的指针域初始化为NULL
  3. 创建邻接表,依次输入每条边依附的两个顶点,确定这两个顶点的序号i和j后,将此边节点分别插入到对应的两个表头节点

2.2.3邻接表表示法的优缺点

优点:

  1. 便于增加和删除顶点(仅需要生成一个边表节点并插入边链表头部即可)
  2. 便于统计边的数目,按顶点表顺序查找所有边表即可,时间复杂度:O(N+e),其中N为顶点数,e为边数
  3. 空间效率高,适合使用稀疏图

缺点:

  1. 不便于判断顶点之间是否有边,要判断(vi,vj)边,就需要查找第i个边表,最坏的情况时间复杂度为O(N)
  2. 不便于计算有向图各个顶点的度,因为使用邻接表只能求出度,使用逆连接表只能求入度

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深度优先搜索

  1. 从图中某个顶点v出发,访问v
  2. 找到刚访问过的顶点的第一个未访问的邻接点,访问该节点,以该节点为新节点,重复此步骤直到刚访问过的节点没有未访问过的节点为止
  3. 返回前一个访问过的且仍有未被访问的邻接点的顶点,然后还是寻找它未被访问过的邻接点并访问
  4. 重复2.3步骤,直到图中全部顶点都被访问了

🔵与树的先序遍历类似

3.2广度优先搜索

  1. 从某个顶点v出发,访问v,使v入列

  2. 只要队列不为空,重复以下操作:

    • 队头元素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到其他顶点的路径

迪杰斯特拉算法:使用带权的邻接矩阵来表示有向网

需要一些辅助数组

  1. 一维数组s:记录从源点到终点是否已被确定了最短路径长度
  2. 一维数组path:记录从源点到终点的当前最短路径上的直接前驱顶点序号,其初值:如果有弧则记录该顶点,无弧则记录-1
  3. 一维数组d:记录从源点到终点的当前路径最短路径长度,其初值:如果有弧则记录权值,无弧则表示无穷大

2.求每一对顶点之间的最短路径(不需要每个顶点经过)

将vi到vj最短路径长度初始化,然后进行n次比较和更新

1.对逐个顶点进行试探

2.从vi到vj的所有存在的路径中选出一条最短的路径

弗洛伊德算法:使用带权的邻接矩阵来表示有向网

需要一些辅助数组

  1. 二维数组path:最短路径上顶点vj的前一顶点的序号
  2. 二维数组d:记录vi,vj之间的最短路径长度

4.3拓扑排序

🔵: 拓扑排序一开始需要先访问一个入度为0的顶点,然后必须先把以该顶点为弧尾的其他顶点先访问完,在以同样的规则访问其他的顶点

AOV—网:用顶点表示活动,用弧表示活动之间的优先顺序的有向图

在网中,若顶点vi到vj有一条有向路径,则vi是前驱,vj是后继,若是一条弧,则称vi是直接前驱,vj是直接后继

拓扑排序:

将AOV-网中所有顶点排成一个线性序列(通俗来说:就是活动A开始的前提条件是活动B已经结束)

拓扑排序的过程:

  1. 在有向图中选一个无前驱的顶点且输出它
  2. 从图中删除该顶点和所有以它为尾的弧
  3. 重复1.2,直到不存在无前驱的顶点

若发现输出的顶点数等于有向图的顶点数,则说明输出的序列是拓扑排序

🔴:一般来说:当顶点连接了多个其他顶点,按照序号顺序大小输出(一个AOV网的拓扑排序不唯一)

需要一些辅助数据结构:

1.一维数组:存放各顶点入度,没有前驱的顶点就是入度为0的顶点

2.栈s:暂存所有入度为0的顶点(可以避免重复查找数组)

3.一维数组:记录拓扑排序的顶点序号

4.4关键路径

⚫️: 图的关键路径是建立在图的拓扑序列之上进行的(即也需要经过全部顶点)

AOE-网:以边表示活动的网,其中,顶点代表事件,弧代表活动,权代表活动持续时间,通常AOE-网用来估算工程的完成时间

源点:入度为0的顶点

汇点:出度为0的顶点

带权路径长度:一条路径中各弧上的权值之和

关键路径:找出一条从源点到汇点的带权路径长度最长的路径

(无论其他路径怎么走,都需要等待关键路径走完,才能到达最后顶点,也就是说整个图的所有顶点访问完所需要的时间只由关键路径决定)

关键活动:关键路径上的活动称关键活动

  1. 事件vi最早发生的时间(多路径选最大值)

等于从源头到vi的最长路径长度

  1. 事件vi最迟发生的时间(多路径选最小值)

等于后继事件vk的关键路径的时间—活动<vi,vk>的持续时间

  1. 活动ai=<ai,aj>最早发生的时间(弧尾顶点最早开始时间)

等于事件vi最早发生的时间

  1. 活动ai=<ai,aj>最迟发生的时间(弧头顶点最迟时间)

等于事件vj的最迟发生的时间—活动ai持续的时间

关键路径算法:使用邻接表作为有向图的存储结构

需要一些辅助数据结构

1.一维数组:事件vi的最早发生时间

2.一维数组:事件vi的最迟发生时间

3.一维数组:记录拓扑序列的顶点序号