数据结构篇_知识点板块_第六章图
数据结构篇为本人考研时所写笔记,包括知识点和编程思想两大板块,笔记内容依据王道数据结构考研书所写,但比书本上知识更加生动形象,愿本篇章能对您有所帮助
六、图
(一)图的基本概念
(二)图的存储
- 邻接矩阵法(又称数组表示法):
① 用一个一维数组存储图中顶点信息,一个二维数组存储图的边信息(不只是存储了顶点的信息)
② 对于带权图而言,若两顶点之间不存在边则用∞表示
③ 对于有向图的第i行非零元素个数正好是顶点i的出度,列正好是入度
④ 稠密图适合用该存储结构
⑤ A^ n的元素A^ n[i][j]:即矩阵乘法,表示当路线长度为n时从i到j时只能找到A^n[i][j]条路径
- 邻接表法:
① 存在两种结点:顶点表结点(表头结点)和边表结点(表结点),其中顶点表中包含顶点域(data)和边表头指针(firstarc),边表(对有向图称为出边表)包含邻接点域(adjvex)和指向下一个邻接边的指针域
② 适合存储稀疏图
③ 计算有向图的入度、度,找入边不方便
④ 可采用逆邻接表的存储方式来加速求解给定顶点的入度
⑤ 表示法不唯一,各结点的链接次序是任意的
- 十字链表:
① 有向图的链式存储结构
② 一个十字链表表示确定一个图
③ 顶点结点中包含data域(存放顶点相关的数据信息),firstin域(指向以该顶点为弧头的第一个弧结点),firstout域(指向以该顶点为弧尾的第一个弧结点,顺着这个走可找到给定结点的出边)
④ 弧结点中有5个域:尾域(tailvex)和头域(headvex)分别指示弧尾和弧头这两个顶点在图中的位置;链域hlink指向弧头相同的下一条弧;链域t1ink指向弧尾相同的下一条弧;info域指向该弧的相关信息。这样,弧头相同的弧就在同一个链表上,弧尾相同的弧也在同一个链表上
- 邻接多重表:
① 无向图的链式存储结构
② 与十字链表类似,每条边用一个结点表示,包含6个域:mark为标志域(用以标记该条边是否被搜索过),ivex和jvex为该边依附的两个顶点一条依附于顶点jvex
在图中的位置;ilink指向下一条依附于顶点ivex的边:jlink 指向下的边,info为指向和边相关的各种信息的指针域
③ 每个顶点也用一个顶点表示,包含两个域:data域存储该顶点的相关信息,firstedge域指示第一条依附于该顶点的边
④ 对无向图而言,其邻接多重表和邻接表的差别仅在同一条边在邻接表中用两个结点表示,而邻接多重表中只有一个结点
邻接矩阵 | 邻接表 | 十字链表 | 邻接多重表 | |
---|---|---|---|---|
空间复杂度 | O(|V|^2) | 无向图O(|V|+2|E|)有向图O(|V|+|E|) | O(|V|+|E|) | O(|V|+|E|) |
找相邻边 | 遍历对应行或列时间复杂度为O(|V|) | 找有向图的入边必须遍历整个邻接表 | 很方便 | 很方便 |
操作 | ① 删除边很方便② 删除顶点需要大量移动数据③ 求有向图顶点的度、判断图中是否有边较方便 | 无向图中删除边或顶点都不方便 | 很方便 | 很方便 |
适用于 | 稠密图 | 稀疏图和其他 | 只能存有向图 | 只能存无向图 |
表示方式 | 唯一 | 不唯一 | 不唯一 | 不唯一 |
(三)图的遍历和图的连通性
-
从图中某一顶点出发访遍图中其余顶点,且使每一个顶点仅被访问一次,这一过程就叫做图的遍历
-
对邻接表存储的图的深度遍历称DFS,对邻接矩阵存储的图深度遍历称DFSM
对邻接表存储的图的广度遍历称BFS,对邻接矩阵存储的图广度遍历称BFSM
-
图的遍历算法可用来判断图的连通性
-
图的广度优先生成树的树高比深度优先生成树树高小或相等
-
代码编写注意:在BFSTraverse()或DFSTraverse()中添加了第二个for循环,再选取初始点,继续进行遍历,以防止次无法遍历图的所有顶点
-
对于无向图,上述两个函数调用BFS(G,i)或DFS (G,i)的次数等于该图的连通分量数
对于有向图,因为一个连通的有向图分为强连通的和非强连通的,它的连通子图也分为强连通分量和非强连通分 量,而非强连通分量一次调用 BFS(G,i)或DFS(G,i)无法访问到该连通分量的所有顶点
(四)图的应用
-
必须学会手工模拟给定图的各个算法的执行过程,此外还需掌握对给定模型建立相应的图去解决问题的方法
-
最小生成树或最小支撑树(MST):
① 概念:对于一个带权连通无向图,所有生成树的集合中权值之和最小的那棵生成树
② 假设G={V,E}是连通图,其最小生成树T=(U,E_T)(E_T是最小生成树中边的集合)
③ 性质:
(1) 最小生成树的树形不唯一,当图的各边权值互不相等时,最小生成树是唯一的
(2) 当G本身是一棵树时,G的最小生成树就是它本身
(3) 最小生成树的边权值之和是唯一的,而且是最小的
(4) 边数=顶点数-1
- Dijkstra:
① Dijkstra提出的一种按路径长度递增序产生各顶点最短路径的算法。
② 按路径长度递增序产生各顶点最短路径:按长度递增的次序生成从源点s到其它顶点的最短路径,则当前正在生成的最短路径上除终点以外,其余顶点的最短路径均已生成(将源点的最短路径看作是已生成的源点到其自身的长度为0的路径)
③ 按照边的权值由小到大的顺序,考察G的边集E中的各条边。(加边法)
- 有向无环图(DAG图)描述表达式:
① 描述含有公共子式的表达式的有效工具
② 与二叉树表示的区别:可以有效实现对相同子式的共享(顶点中不可能出现重复的操作数),从而节省存储空间
③ 有向无环图描述表达式形态不唯一
④ 对于化简题的解题方法
(1) 把各操作数不重复的排成一排
(2) 标出各个运算符的生效顺序
(3) 按顺序加入运算符,注意“分层”(若运算符A在运算符B的上一层说明A基于B/基于B这层其他运算符的计算结果而进行的下一步计算)
(4) 从底向上逐层检查同层的运算符是否可以合体
- 拓扑排序
① AOV网(又称顶点表示活动的网络):若用DAG图表示一个工程,其顶点表示活动,用有向边<V_i,V_j>表示活动V_i必须先于V_j进行
② 是一个有向无回路的图
③ 任何V_i不能以自己作为自己的前驱或后继
④ 拓扑排序:由一个有向无环图的顶点组成的序列,且满足:
(1) 每个顶点出现且只出现一次
(2) 若顶点A在序列排在顶点B前,则在图中不存在从顶点B到顶点A的路径
⑤ 每个AOV网都有一个或多个拓扑排序
⑥ 若图中无环,也可用深度优先遍历进行拓扑排序,此时按DFS函数先后记录的结点序列为逆向的拓扑有序序列
⑦ 写拓扑排序的步骤(算法思想,近期期末考了):
(1) 从AOV网中选择一个没有前驱的顶点并输出(若选择的是没有后继则是逆拓扑排序)
(2) 从网中删除该顶点和所有以它为起点的有向边
(3) 重复①②直到为空或不存在无前驱结点的顶点为止,后一种说明有向图中必然存在环
⑧ 用邻接表存储时间复杂化度O(|V|+|E|)
用邻接矩阵存储时间复杂化度O(|V|^2)
⑨ 注意:
(1) 若各个顶点已经排在一个线性有序的序列中,每个顶点有唯一的前驱后继关系,则拓扑排序的结果唯一
(2) 若邻接矩阵是三角矩阵,则存在拓扑序列(不一定唯一),反之则不一定成立
(3)若两个结点之间不是祖先或子孙关系,则它们在拓扑排序中的关系是任意的(前后关系任意),因此使用栈和队列都可以,因为进栈或队列的都是入度为0的结点,此时入度为0的所有结点是没有关系的
(4) 若有向图的拓扑排序唯一,则图中每个顶点的入度和出度不一定最多为1
(5) 有向无环图的拓扑排序序列唯一并不能唯一确定一个图
(6) 有向图为一个无环图则一定存在拓扑序列
- 关键路径:
① AOE网(又称用边表示活动的网络):在带权有向图中,以顶点表示事件,以有向边表示活动,以边上权值表示完成该活动的开销
② 是带权的有向无环图
③ 开始顶点(源点):仅有的一个入度为0的顶点
结束顶点(汇点):仅有的一个出度为0的顶点
关键路径长度:完成整个工程的最短时间,即关键路径上各活动花费开销总和
工程结束:所有路径上的活动都已完成
④ 求解关键路径步骤:
(1) 从源点出发,令ve(源点)=0,按拓扑有序求其余顶点的最早发生时间ve()
(2) 从汇点出发,令vl(汇点)= ve(汇点),按逆拓扑有序求其余顶点的最迟发生时间vl()
(3) 根据各顶点的ve()值求所有弧的最早开始时间e()
(4) 根据各顶点的vl(Q值求所有弧的最迟开始时间l()
(5) 求AOE网中所有活动的差额d(0, 找出所有d()= 0的活动构成关键路径
⑤ 注意:
(1) 当缩短到一-定程度时, 关键活动可能会变成非关键活动
(2) 可能有多条关键路径,只提高一条关键路径上的关键活动速度并不能缩短整个工程的工期,只有加快那些包括在所有关键路径上的关键活动才能达到缩短工期的目的。
(3) 关键活动一定位于关键路径上
参量及计算 | ||
---|---|---|
参量 | 概念 | 计算方法 |
事件V_k的最早发生时间ve(k) | 从源点到该顶点的最长路径长度决定了所有从此结点开始的活动能够开工的最早时间 | 按从前往后的顺序计算(选取最大的,因为一个结点开始执行要求前面的结点都已执行完,即拓扑排序的入度为0)(选最大的) |
事件V_k的最迟发生时间vl(k) | 在不推迟整个工程完成的前提下,该事件最迟必须发生的时间 | 按从后往前的顺序计算(选最小的) |
活动a_i的最早开始时间e(i) | 活动弧的 起点表示的事件的最早发生时间 | e(i)=ve(k) |
活动a_i的最早开始时间l(i) | 活动弧的终点所表示的事件最迟发生时间与该活动所需时间之差 | 若<vk,vj>表示活动a_i,则有l(i)=vl(j)-Weight(vk,vj) |
活动a_i的时间余量d(i) | d(i)=0的活动就是关键活动 | d(i)=l(i)-e(i) |