数据结构与算法 --3. 图

一、图的定义

图 $G$ 由顶点集 $V$ 和边集 $E$ 组成,记为 $G=(V,E)$
$V={v_1,v_2,…,v_n}, \quad |V|$ 表示图中顶点的个数,也称为图 G 的阶
$E=\lbrace (u,v)|u \in V,v \in V \rbrace$
注释:图不能为空集

二、图的分类和术语

1. 有向图 / 无向图

2. 简单图 / 多重图

简单图:(1) 不存在重复边 (2) 不存在 $(u,u)$ 点

3. 完全图 (有向完全图 / 无向完全图)

任意两个点 $(u,v)$ 之间存在边 $(u,v)$

4. 子图

对于图 $G=(V,E)$ 和图 $G’=(V’,E’)$,如果 $V’ \in V$,$E’ \in E$,则 $G’$ 是 $G$ 的一个子图,默认图的自身也是子图

5. 连通,连通图,连通分量(无向图中考虑)

连通:当且仅当 $u,v$ 存在一条路径,称为 $u,v$ 连通
连通图:图中任意两点皆连通
连通分量:极大连通子图

6. 强连通,强连通图,强连通分量(有向图中讨论)

7. 生成树,生成森林

只有连通图可以考虑生成树,非连通图只能考虑生成森林
生成树:极小连通子图 $V’=V$(极小指的是边最少)
生成森林:非连通图的每个连通分量都有一个生成树

8. 顶点的度(无向图或无向图),入度 / 出度(有向图)

$d_i:v_i$ 的边数
$\sum d_i = 2e$
$\sum id_i = e$
$\sum od_i = e$

9. 边的权值,网 (带权图)

10. 稠密图,稀疏图(没有严格的定义)

$|E| < |V|log|V|$

11. 路径,路径长度,回路 / 环 / 圈

路径长度:$u$ 到 $v$ 经过的边数
$e < n-1$:非连通
$e > n-1$:一定有环

12. 简单路径,简单回路

简单路径:顶点不重复
简单回路:除首末节点,不存在重复顶点

13. 距离

若 $u,v$ 存在路径,最短的路径长度称为距离

14. 有向树

${\exists} v_o, \;\; id_{v_o}=0$
其余 $v_i \;\; id_{v_o}=1$

三、图的存储和基本操作

1. 邻接矩阵法:有 $n$ 个顶点,用一个 $n \times n$ 的矩阵存储

$a [i,j]$ 用来表示顶点 $i$ 到顶点 $j$ 是否有边
有向权值图的权值可能是 0 也可能是负数,所以用 $\infty$ 表示 $u,v$ 之间不存在边
$A^n [i][j]$:表示顶点 $i$ 到顶点 $j$ 当中路径长度 $=n$ 的路径条数(后面会证明)

2. 邻接表

优点:方便找到与顶点相邻的边

3. 十字链表


Q:怎么画十字链表?

4. 邻接多重表

四、图的遍历

1. 广度优先搜索 BFS

操作

标记起始节点,并把起始节点入队
从队列中弹出一个元素,访问,并把该元素的未标记的相邻节点入队

复杂度

时间复杂度:$\begin {cases}
O (n^2), & \text {邻接矩阵} \\
O (n+e), & \text {邻接表}
\end {cases}$
空间复杂度:$O (n)$

无权值图的最短路径
广度优先生成树

2. 深度优先搜索 DFS

操作

访问相邻的未标记节点
如果没有相邻的未标记节点,则向上回溯

复杂度

时间复杂度:$\begin {cases}
O (n^2), & \text {邻接矩阵} \\
O (n+e), & \text {邻接表}
\end {cases}$
空间复杂度:$O (n)$

连通性、连通片数量

如果回溯到了起始节点,但是还有未标记的节点,代表图是非连通的
则在未标记的节点中随机选择一个节点,并重复遍历动作
可以根据 DFS 被调用的次数,判断连通片的数量

深度优先生成树

五、最小生成树

1. 概念

生成树:连通图中的最小连通子图(边最少)
最小生成树:带权图中权值和最小的生成树
不同的最小生成树,权值合都是相同的

2. 算法

(1) Prim 算法:保持连通,选最小边及其顶点(最小边 = 最权值小的边)

从某一个顶点开始,不断加顶点和边
思路:贪心算法(每一步都考虑当前最优,结果恰好是全局最优,前提是满足最优子结构)
时间复杂度:$O (n^2)$

(2) Krushal 算法:保持无圈,选最小边

先包含全部的节点,因为生成树的节点数都是一样的
思路:贪心算法
时间复杂度:$O (eloge)$

(3) 破圈法:找一个圈,删除最大边


圈:$abdeca$,删除 $de$ 边

圈:$abdca$,删除 $dc$ 边

圈:$abeca$,删除 $be$ 边

六、最短路径

1.Dijkstra 算法:求单源最短距离(权值非负的有向图)

思想:贪心算法

(1) 将每个点标记一个距离值

(2) 选取最小的顶点,加进来
(3) 修改该点的出边的距离值

若通过该点到达的距离比较近,则修改距离值,并标记前驱节点

(4) 重复该过程,直到全部的点被加进来



时间复杂度:$O (n)$
例:节点 1 到节点 4 的最短路径?
找节点 4 的前驱标记,是 3;再找节点 3 的前驱标记,依次重复
1->3->2->4

2.Floyd 算法:求各顶点值间最短路径(可以有负权值,但不可以有含负权值的环)

思想:动态规划
$A^{(k)}_{i,j}$:$i$->$j$ 在顶点集 $\lbrace 1,2,…,k \rbrace$ 中的最短路径
路径集合 $S^{(k)}$ 有两种:
(1) 包含 k 的:$A^{(k-1)}_{i,k}+A^{(k-1)}_{k,j}$

(2) 不包含 k 的:$A^{(k-1)}_{i,j}$
于是可以推得公式:$A^{(k)}_{i,j}=min \lbrace A^{(k-1)}_{i,j},A^{(k-1)}_{i,k}+A^{(k-1)}_{k,j} \rbrace$

七、拓扑排序

应用场景:有向无环图 (AOV 网),顶点代表的是事件

$i$->$j$:$i$ 必须完成,才能开始 $j$

(1) 选择一个入度 = 0 的点

(2) 删除该点的所有出边

(3) 重复这两个步骤,直到

$\begin {cases}
没有点了:得到一个合法的拓扑排序 & \text {} \\
有点但是没有入度为 0 的点:图中有环 & \text {}
\end {cases}$

八、关键路径

1. 概念

应用场景:有向带权图,顶点代表事件,边代表活动,权值代表开销 (时间)->AOE 网
顶点不是重点,顶点只是表示入边代表的事件已经完成了
关键路径:在 AOE 网中寻找最长路径(最短工期:完成 $e_1-e_5$ 最少需要的时间)
关键路径是不唯一的
目的:缩短关键路径,缩短工期
关键活动:关键路径上的边所代表的活动

2. 算法概念


(1) $ve (k)$:顶点 k 的最早时间
$ve ($ 源点 $)=0$
$ve (k)=max \lbrace ve (i)+w (i,k) \rbrace$
(2) $vl (k)$:顶点 k 最迟时间
(3) $ve (k)=vl (k)$:工期不变
$vl (k$$)=min \lbrace vl (j)-w (k,j) \rbrace$
(4) $e ()$:边的最早时间
$e (e_ik)=ve (i)$
(5) $l ()$:边的最迟时间
$l (e_kj)=l (j)-w (k,j)$
(6) $d ()=l ()-e ()$
$d ()=0$ 是关键活动

3. 例子

4. 缩短工期:缩短所有关键路径

(1) 公共边
(2) 每个路径选一个

5. 总结

$ve ()$:起点加权取最大
$vl ()$:终点减权取最小
$e ()$:就取起点最早值
$l ()$:终点最迟减权值

数据结构与算法系列

数据结构与算法 —1. 线性表
数据结构与算法 —2. 树
数据结构与算法 —3. 图
数据结构与算法 —4. 查找
数据结构与算法 —5. 排序