InkSoul/content/408/《数据结构》图.md

324 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

---
title: "《数据结构》图"
date: 2023-08-01T11:26:56+08:00
---
## 概念
### 基本概念
* 定义
* 图 = 顶点 + 边
* 表示
* 图G记为G=(V,E)
* 注意
* 线性表有空表,树有空树,但图没有空图
* 图的顶点集V一定非空但边集E可以为空
* |V|表图G中顶点的个数也称图G的阶
* |E|表图G中边的个数
* 示例
* G = (V,E)
* V = {1,2,3,4,5,6}
* E = {(1,2),(2,3),(3,4),(4,5),(1,5),(5,6),(2,6),(3,6)}
* ![](../../images/408/《数据结构》图/无向图实例.png)
### 常考结论
* 无向图边数*2 = 各顶点度数之和
* 有向图边数 = 各顶点的入度之和 = 各顶点的出度之和
* 一个连通图的生成树是一个极小连通子图,是无环的
* 完全无向图:边数$\frac{n(n-1)}{2}$
* 完全有向图:边数为$n(n-1)$
* 对于一个有n个顶点的图G
* 若是连通无向图其边的个数至少为n-1边最少即构成一棵树的情形
* 若是强连通有向图其边的个数至少是n边最少即构成一个有向环的情形
* 对n个顶点的无向图G
* 所有顶点度之和 = 2|E|
* 若G为连通图则至少有n-1条边
* 若|E|>n-1则一定有回路
* 若G是非连通图则最多可能有$C^2_{n-1}$条边
* 无向完全图有$C^2_n$
* 对n个顶点的有向图G
* 所有顶点的出度之和 = 入度之和 = |E|
* 所有顶点的度之和 = 2|E|
* 若G为强连通图则最少有n条边形成回路
* 有向完全图共有$2C^2_n$条边
### 常见术语
* 无向图
* 全部由无向边构成的图
* ![](../../images/408/《数据结构》图/无向图.png)
* 有向图
* 全部由有向边构成的图
* ![](../../images/408/《数据结构》图/有向图.png)
* 简单图、多重图
* 简单图:不存在重复边,不存在顶点到自身的边
* 多重图两个顶点之间的边数大于1
* 完全图(简单完全图)
* 完全无向图:边数$\frac{n(n-1)}{2}$
* 完全有向图:边数为$n(n-1)$
* ![](../../images/408/《数据结构》图/完全无向图.png)
* ![](../../images/408/《数据结构》图/完全有向图.png)
* 子图、生成子图
* G的子图所有顶点和边
* G的生成子图含有G的所有顶点
* ![](../../images/408/《数据结构》图/生成子图.png)
* 连通、连通图、连通分量【无向图】
* V和W连通无向图中V到W的路径存在
* 连通图:图中任意两个顶点都是连通的
* 连通分量:无向图中的极大连通子图
* 强连通图,强连通分量【有向图】
* V和W连通有向图中从到W和从W到V都有路径
* 强连通图:图中任何一个一对顶点都是强连通的
* 强连通分量:有向图中的极大连通子图
* 生成树,生成森林
* 顶点的度Degree图中与该顶点相关联边的数目
* 入度:指向该顶点的边的数目
* 出度:从该顶点出去的边的数目
* 顶点的度 = 出度 + 入度
* 对于具有n个顶点e条边的有向图出度和 = 入度和 = e对于具有n个顶点e条边的无向图度和 = 2e
* ![](../../images/408/《数据结构》图/顶点的度.png)
* 边的权和网
* 权Weight:边上的数值
* 网Network边上标识权的图
* ![](../../images/408/《数据结构》图/边的权和网.png)
* 稠密图,稀疏图
* 稠密图:边多
* 稀疏图:边少
* ![](../../images/408/《数据结构》图/稠密图.png)
* 路径、路径长度和回路
* 路径Path在一个图中路径是从顶点u到顶点v所经过的顶点序列
* 路径长度:该路径上边的数目
* 回路:第一个顶点和最后一个顶点相同的路径
* 简单路径,简单回路
* 简单路径:顶点不重复出现的路径
* 简单回路:除第一个和最后一个顶点,其余顶点不重复出现的回路
* 距离
* 从u到v的距离从u到V的最短路径长度
* 有向树:一个顶点的入度为0其余顶点的入度均为1的有向图
## 图的存储和基本操作
### 常考结论
* 求有向图节点的入度,必须遍历整个邻接表
* 有向图邻接表中,删除某个顶点的所有边的时间复杂度 = O(n+e)
* 一个图的邻接矩阵表示唯一,邻接表表示不唯一
### 图的存储方式
* 邻接矩阵
* 用两个数组来表示图
* 一个一位数组存储图中顶点信息
* 一个二维数组存储图中的边或弧的信息
* ![](../../images/408/《数据结构》图/邻接矩阵.jpg)
* 邻接表
* 邻接表是由两部分组成
* 顶点用一个一维数组存储
* 每个顶点的所有邻接点构成一个线性表
* 由于邻接点的个数不定,所以用单链表存储
* 无向图称为顶点Vi的边表
* 有向图则称为顶点Vi作为弧边的出边表
* ![](../../images/408/《数据结构》图/邻接表.jpg)
* 十字链表【针对有向图】
* 把邻接表和逆邻接表整合在了一起
* 既容易找到以Vi为尾的弧也容易找到以Vi为头的弧
* 容易求得顶点的出度和入度
* 创建图算法的时间复杂度和邻接表相同
* ![](../../images/408/《数据结构》图/十字链表.jpg)
* 邻接多重表【针对无向图】
* 仿照十字链表的方式,对边表结点的结构进行一些改造
* 同一条边在邻接表中用两个结点表示,而在邻接多重表中只有一个结点
* ![](../../images/408/《数据结构》图/邻接多重表.jpg)
### 图的基本操作
|函数|功能|无向邻接矩阵时间复杂度|有向邻接矩阵时间复杂度|无向邻接表时间复杂度|有向邻接表时间复杂度|
|---|---|---|---|---|---|
|Adjacent(G,x,y)|判断是否存在边|O(1)|O(1)~O(N)|O(1)|O(1)~O(N)
|Neighbors(G,x)|列出图中与结点x邻接的边|O(V)|O(1)~O(V)|O(V)|出边O(1)~O(V)<br>入边:O(E)|
|InsertVertex(G,x)|在图中插入顶点x|O(1)|O(1)|O(1)|O(1)|
|DeleteVertex(G,x)|在图中删除顶点x|O(V)|O(V)|O(1)~O(E)|出边O(1)~O(V)<br>入边:O(E)|
|AddEdge(G,x,y)|若无向边或有向边不存在,则向图中添加该边|O(1)|O(1)|O(1)|O(1)|
|RemoveEdge(G,x,y)|若无向边或有向边存在,则向图中删除该边|O(1)~O(V)|O(1)~O(V)|O(1)|出边O(1)<br>入边:O(1)~O(E)|
|NextNeighbor(G,x,y)|假设图中顶点y是顶点x的一个邻接点<br>返回除y外的顶点x的下一个邻接点的顶点号<br>若y是x的最后一个邻接点则返回1|O(1)~O(V)|O(1)~O(V)|O(1)||
|Get_Edge_Value(G,x,y)|获取图中对应的权值|O(1)|O(1)|O(1)~O(V)||
|Set_Edge_Value(G,x,y,v)|设置图中边对应的权值|O(1)|O(1)|O(1)~O(V)||
## 图的遍历
### 深度优先搜索DFS
* 相当于树的先序遍历
* 根节点就是任意出发的节点
* 子节点就是所有邻近的未访问的节点
* 时间复杂度【N为顶点】【E为边】
* 邻接表存储
* O(N + E)
* 邻接矩阵存储
* O($N^2$)
* 空间复杂度
* 最好情况O(1)
* 最坏情况O(n)
* 遍历过程
* ![](../../images/408/《数据结构》图/DFS遍历过程.jpg)
* 生成树
* ![](../../images/408/《数据结构》图/DFS生成树.jpg)
* 生成森林
* ![](../../images/408/《数据结构》图/DFS生成森林.jpg)
### 广度优先搜索BFS
* 相当于树的层序遍历
* 时间复杂度【N为顶点】【E为边】
* 邻接表存储
* O(N + E)
* 邻接矩阵存储
* O($N^2$)
* 空间复杂度
* O(n)
* 遍历过程
* ![](../../images/408/《数据结构》图/BFS遍历过程.jpg)
* 生成树
* ![](../../images/408/《数据结构》图/BFS生成树.jpg)
* 生成森林
* ![](../../images/408/《数据结构》图/BFS生成森林.jpg)
### 遍历次数
* 无向图
* 调用BFS/DFS次数 = 连通分量数
* 对于连通图只需要调用1次BFS/DFS
* 有向图
* 调用BFS/DFS次数依情况分析
* 起始顶点到其他各顶点都有路径只需要调用1次BFS/DFS
* 对于强连通图从任一结点出发都只需要调用1次BFS/DFS
## 图的应用
### 常考结论
* 最短路径一定是简单路径
* 求最短路径是允许图有环的
* 一个有向图的定点不能排列成一个拓扑序列表明其中存在一个顶点数目大于1的回路该回路构成一个强连通分量
* 若有向无环图的拓扑序列唯一,不能唯一确定该图
### 最小生成树
* 定义图G的所有生成树中边的权值之和最小的树
* 性质
* 最小生成树并不是唯一的
* 当图G的各边权值不相等时最小生成树是唯一的
* 当G本身就是一棵树时其最小生成树就是它本身
* 最小生成树的边的权值之和总是唯一的
* 最小生成树的边数 = 顶点数-1
* 算法1Prim算法
* 类似于寻找图的最短路径的Dijkstra算法【以自我为中心】
* 当带权连通图的任意一个环中所包含的边权值不同时,最小生成树是唯一的
* ![](../../images/408/《数据结构》图/Prim算法.jpg)
* 算法2Kruskal算法
* 按权值的递增次序选择合适的边来构造最小生成树【不以自我为中心】
* ![](../../images/408/《数据结构》图/Kruskal算法.jpg)
### 最短路径
* BFS
* 求无权图的单源最短路径问题
* ![](../../images/408/《数据结构》图/求无权图的单源最短路径问题.jpg)
* Dijkstra
* 求有向图的单源最短路径问题
* ![](../../images/408/《数据结构》图/求有向图的单源最短路径问题.jpg)
* Floyd算法
* 求任意两顶点的最短路径
* ![](../../images/408/《数据结构》图/求任意两顶点间的最短路径.jpg)
### 有向无环图描述表达式
* 有向无环图定义
* 若一个有向图中不存在环则称为有向无环图简称DAG图
* 利用有向无环图描述表达式【顶点中不能出现重复操作数】
* ![](../../images/408/《数据结构》图/利用有向无环图描述表达式.jpg)
### 拓扑排序优先入度为0
* 核心算法
* ![](../../images/408/《数据结构》图/拓扑排序.jpg)
* 时间复杂度
* 采用邻接矩阵存储,时间复杂度为$O(|V|^2)$
* 采用邻接表存储时间复杂度为O(|V|+|E|)
### 逆拓扑排序优先出度为0
* 核心算法
* ![](../../images/408/《数据结构》图/逆拓扑排序.jpg)
### 关键路径
* 常考点
* 计算最早开始时间和最晚开始时间
* 关键路径是指权值之和最大而非边数最多的路径
* 无论存在一条或多条关键路径,增加任一关键活动的时间都会延长工程的工期
* 只有一条关键路径的时候,减少关键活动的时间会缩短工程的工期
* 有多条关键路径的时候,减少关键活动的时间不一定会缩短工程的工期
* 求关键路径的快速方法:找起点到终点的最长路径
* AOE网
* ![](../../images/408/《数据结构》图/AOE网.jpg)
* 计算过程
![](../../images/408/《数据结构》图/计算过程.jpg)
1. 求所有事件的最早发生事件Ve()
Ve(源点) = 0
ve(k) = max{Ve(i)+Weight(Vj,Vk)}
拓扑排序V1V3V2V5V4V6
Ve(1)=0,Ve(3)=2,Ve(2)=3,Ve(5)=6
Ve(4) = max{Ve(2)+2,Ve(3)+4} = 6
Ve(6) = max{Ve(5)+1,Ve(4)+2,Ve(3)+3} = 8
2. 求所有事件的最迟发生时间Vl()
Vl(汇点) = Ve(汇点)
Vl(k) = Min{Vl(j)-Weight(Vk,Vj)}
逆拓扑排序V6,V5,V4,V2,V3,V1
Vl(6)=8,Vl(5)=7,Vl(4)=6
Vl(2) = min{Vl(5)-3,Vl(4)-2} = 4
Vl(3) = min{Vl(4)-4,Vl(6)-3} = 2
Vl(1) =0
||V1|V2|V3|V4|V5|V6|
|---|---|---|---|---|---|---|
|Ve(k)|0|3|2|6|6|8|
|Vl(k)|0|4|2|6|7|8|
||a1|a2|a3|a4|a5|a6|a7|a8|
|---|---|---|---|---|---|---|---|---|
|e(i)|0|0|3|3|2|2|6|6|
|l(i)|1|0|4|4|2|5|6|7|
|l(i)-e(i)|1|0|1|1|0|3|0|1
3. 求所有活动的最早发生时间e()
<Vk,Vj>表示活动$a_i$,则有e(i) = Ve(k)
4. 求所有活动的最迟发生时间l()
<Vk,Vj>表示活动$a_i$,则有l(i) = vl(j) - Weight(Vk,Vj)
5. 求所有活动的时间余量d()
d(i) = l(i) - e(i)
此时根据l(i)-e(i)=0的关键活动得出关键路径为(V1,V3,V4,V6)