图这部分以概念为主,具体实现放到算法部分
参考:王道数据结构图部分
4.1 基本概念
不存在空图
顶点集一定非空
边集可以为空
图和树是逻辑上的区别,都是 逻辑结构
4.1.1 顶点的度
4.1.2 图的种类
1. 简单图
- $v_i \rightarrow v_j$ 间无重复边
- 任一顶点无自身到自身的顶点
2. n个顶点的完全图
3. 子图
顶点集为图G的子集
- 并非所有的顶点子集与边子集的组合都是图G的子图
- 任一条边及其两个端点都在的边集与顶点集的组合才构成子图
4. 连通图[无向图]
无向图连通:顶点 $v\rightarrow w$ 直接有路径
图 G 中任两顶点间都连通
- 一点可以访问全部
- 若边数小于n-1,则一定是非连通图
- 连通分量:无向图中极大连通子图
- 生成树:连通图 的包含所有结点的极小连通子图
- 包含n-1条边,n个顶点
- 加一条边构成回路,减一条边为非连通图
- 生成森林:非连通图 的多个连通分量生成的多个树构成的森林
5. 路径&完全图
路径
$v_p \rightarrow v_q$ 之间的顶点序列
- 路径长度:路径中的顶点个数
- 距离:$v_p \rightarrow v_q$ 的最短路径长度
- 简单路径:顶点不重复
回路、环:起点和终点 ($v_p == v_q$) 相同的路径
- 简单回路:除首尾顶点不重复的路径
- 有拓扑序列的图一定无环
完全图
已知有n个顶点,确保连通的的最小边数为:n-1个顶点的完全图的边数+1,$\frac{(n-1)(n-2)}{2}+1$
已知有n条边,组成非连通图的顶点数为:k个顶点组成完全图+1,$\frac{k(k-1)}{2}+1\Rightarrow \lceil \sqrt{2n} \rceil+1$
6. 强连通图[有向图]
有向图连通:$v \rightleftarrows w$ ,双向连通
每对顶点间有 路径
- 不是每对顶点间都有 弧
强连通分量
有向图中的极大强连通图
有向完全图一定是强连通图
4.2 图的存储
4.2.1 邻接矩阵法
1. 规则
- 表示
无向图
邻接矩阵对称且唯一
可进行压缩存储,只存放上(下)三角矩阵元素
对称矩阵默认为无向图
第i行(列)非零元素个数= $v_i$ 的出度OD( $v_i$ )或者入度ID( $v_i$ )
有向图
- 第i行非零且非 $\infty$ 的元素个数为顶点 $v_i$ 的出度
- 第i列非零且非 $\infty$ 的元素个数为顶点 $v_i$ 的入度
2. 特点
适用于 稠密图 存储
空间复杂度为 $O(\mid v\mid^2)$
确定两点之间是否有边 $O(1)$ ——数组的随机存取特性
确定图中边数 $O(\mid v\mid^2)$
某个顶点出度越大,则存储矩阵的行非零元素越多
图G的邻接矩阵为A,则$A^n[i][j]$ 表示从 $v_i \rightarrow v_j$ 的长度为n的路径数量
1 | typedef struct{ |
1. 特点
适用于 稀疏图
邻接表 不唯一
同一顶点的边结点连接顺序不唯一,取决于建立边表的算法及边的输入序列
存储空间
无向图:$O(\mid v\mid+2\mid e\mid)$
有向图:$O(\mid v\mid+\mid e\mid)$
时间复杂度
找所有邻边:读顶点的边表——$O(n)$
确定边是否存在:扫一个端点的边表—— $O(n)$
有向图某顶点的度
出度:邻接表中结点个数
入度:需遍历全部邻接表
2. 表示
1 | typedef struct{ |
4.2.3 邻接多重表
无向图的存储结构
4.2.4 十字链表法
有向图的存储结构
4.3 图的基本操作
独立于存储结构
参数相同;实现不同,性能不同
1 | Adjacent(G,x,y);//x与y之间是否存在边 |
已知n个顶点的图,其邻接矩阵表示为MGraph,邻接表表示为LGraph
判别图中边数 n(e)
n(x):表示x的个数
无向图 | 有向图 | |
---|---|---|
MGraph | $n(e)=\frac{n(1)}{2}$ | $n(e)=n(1)$ |
LGraph | $n(e)=\frac{n(eNode)}{2}$ | $n(eNode)$ |
- 判断两点是否连通
MGraph | $MGraph[i][j] \overset{?}{=}1$ | $O(1)$ |
---|---|---|
LGraph | 遍历顶点i的邻接表 | $O(e)$ |
- 度的计算
无向图 | 有向图 | |
---|---|---|
MGraph | $2*第 i 行 ‘1’ 的个数$ | 出度:第 i 行 ‘1’ 的个数 入度:第 i 列 ‘1’ 的个数 |
LGraph | n(eNode) | $出度:表头为 i 的单链表中 eNode 的个数$ $入度:边表中 i 的个数$ |
4.3 图的遍历
从某一顶点出发,沿图中的边对图中所有顶点访问且只访问一次
遍历与经过的区别
从某一点出发经过图中所有结点 $\neq$ 遍历:每个结点不重复的访问一次
对每个顶点查找邻接点的过程取决于存储结构
矩阵: $O(\mid v\mid^2)$
邻接表:$O(\mid e\mid)$
树是一种特殊的图
4.3.1 广度优先搜索
从某一顶点 v 开始,由近到远访问和 v 有路径长度为1,2,3….的顶点
- 逐层访问,故需要借助队列
1. 实现
1 | bool visted[MAX_VERTEX_NUM]; |
2. 性能分析
空间复杂度
$O(\mid v\mid)$
时间复杂度[与采取的存储方式有关]
邻接矩阵 $O(\mid v\mid^2)$
邻接表 $O(\mid v\mid+\mid e\mid)$
顶点入队 $O(\mid v\mid)$
搜索所有邻接点,访问边 $O(\mid e\mid)$
3. 应用
求 $u \rightarrow v$ 路径长度最小的路径
无权图求单源点最短路径
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19void BFS_Min_Distance(Graph G,int u){
//d[i]表示从u到i的最短路径
for(i = 0;i < G.vexnum;++i)
d[i] = INFINITE;
visited[u] = TRUE;
d[u] = 0;
EnQueue(Q,u);
while(!isEmpty(Q)){
DeQueue(Q,u);//BFS算法主过程
for(w = FirstNeighbor(G,u);w >= 0;
w = NextNeighbor(G,u,w)){
if(!visited[w]){
visited[w] = TRUE;//设已访问标记
d[w] = d[u]+1;
EnQueue(Q,w);
}
}
}
}广度优先生成树
邻接矩阵:表示法唯一 $\Longrightarrow$ 生成树唯一
邻接表:表示法不唯一 $\Longrightarrow$ 生成树不唯一
4.3.2 深度优先搜索
DFS——递归算法,用一个递归工作栈,空间复杂度 $O(\mid v\mid)$
性能分析
时间复杂度
邻接矩阵 $O(\mid v\mid^2)$
邻接表 $O(\mid v\mid+\mid e\mid)$
空间复杂度
$O(\mid v\mid)$
1 | bool visited[MAX_VERTEX_NUM];//访问标记数组 |
4.3.3 图的遍历与连通性
连通
- 无向图:从某一顶点 v 出发,一次遍历可访问图中所有顶点
- 有向图:初始点到图中每个顶点都有路径
连通分量个数
DFSTraverse
/BFSTraverse
中调用DFS
/BFS
次数为连通分量数
对一个有向无环图,
DFSTraverse
的退栈序列就是一个拓扑序列
4.4 应用
4.4.1 最小生成树
边数 = 顶点数 - 1
带权连通图权值和最小
最小权值和唯一,但树形不唯一
若各边权值互不相等,则树形唯一
1 | Generate_MST(Graph G){ |
1. 深度优先、广度优先生成树
2. Prim算法
选点:Prime——Point
手动模拟
实现
1 | void Prim(G,T){ |
时间复杂度:$O(\mid v\mid^2)$ ,故不依赖于边集,适用于稠密图
3. Kruskal算法
1 | void Kruskal(G,T){ |
时间复杂度:$O(\mid e\mid log\mid e\mid)$ ,适用于点多边少
4.4.2 最短路径
带权路径长度:路劲上权值的和
- 满足连通的 $u \rightarrow v$
- 找所有 $u \rightarrow v$ 中权值最小的路径
区别:
Dijkstra
: 单源点最短路径
Floyd
: 多源点最短路径
1. Dijkstra
1 | void Dijkstra(MGraph G,int v0,Patharc P,ShortPathTable D){ |
2. Floyd
4.4.3 有向无环图
有向无环图:一个有向图中无环,简称DAG
用于描述公共子式
- 符号最多为二元运算符
- 出现多少种运算数就多少个叶结点
4.4.4 拓扑排序
在一个有向无环图的顶点组成的序列中
- 每个顶点出现且只出现一次
若顶点A在序列中排在顶点B前面,则在图中不存在B->A的路径
对有向无环图的一种排序,若存在一条顶点A到顶点B的路径,则在排序中顶点B出现在顶点A的后面。
1. AOV
AOV(用顶点表示活动):若用DAG表示一个工程,顶点表示活动,有向边 $
$ 表示活动 $v_i$ 必须先于活动 $v_j$ 发生的关系。
- $v_i$ 是 $v_j$ 的直接前驱,$v_j$ 是 $v_i$ 的直接后继,具有传递性
- 任何活动 $v_i$ 都不能以自身作为前驱或者后继
2. 拓扑排序步骤
每次从AOV中选一个没有前驱(入度=0)的顶点输出
从AOV网中删除该顶点和以该顶点为起点的所有边
重复 1. 2. 直至当前的AOV为空或当前网中不存在无前驱的顶点
若AOV为空,则该图存在拓扑序列
若AOV不空,则该图不存在拓扑序列
3. 拓扑排序实现
1 | bool TopologicalSort(Graph G){ |
时间复杂度:$O(\mid v\mid+\mid e\mid)$
入度为零的顶点,即没有前驱活动的或前驱活动全部完成的顶点,工程可以从这个顶点所代表的活动开始或继续
- 若一个顶点有多个直接后继,则拓扑序列不唯一
- AOV网中各个顶点地位相同,故可重新编号,若是DAG则其邻接矩阵是三角矩阵
4.4.5 关键路径
1. AOE
有向无环图中,顶点表示事件,有向边表示活动,边上权值表示完成该活动的开销。用边表示活动的图为AOE。
- 只有在顶点所代表的事件发生后,从该顶点出发的有向边代表的活动才能开始
- 只有在进入某顶点各有向边所代表的活动完成后,该顶点所代表的事件才能发生
- AOE网中,只有一个入度为0的顶点(源点),表示整个工程开始;一个出度为0的顶点(汇点),表示整个工程结束
拓扑序列:$v_1,v_2,v_3,v_4,v_5,v_5,v_6,v_7,v_8,v_9$
2. 关键路径
从源点到汇点的路径中,具有最大路径长度的路径;关键路径上的活动为关键活动
- 加快关键活动可以缩短整个工程的工期,但缩短到一定程度,关键活动会为非关键活动
- 若AOE网中关键路径不唯一,只有加快公共的关键路径上的关键活动,才能缩短工期
3. 活动的最早开始时间最晚开始时间
活动最早开始时间 = 活动的起点事件最早开始时间
活动的最晚开始时间 = 活动的终点事件最晚开始时间-活动的时长
活动的最晚开始时间-活动的最早开始时间 = 0 的活动为关键活动
即关键路径为($v_1,v_4,v_7,v_8,v_{10},v_{11}$)