1. 图的概念
学了离散数学和数据结构之后,对图的一个概念应该是要比较清晰的。从概念上说,应当是一个点集和一个边集。注意,这里的集合是严格的数学上的集合,即满足确定性、无序性、唯一性。当然,从中也可以看出有向图和无向图之间的区别了。
1.1. 图的数学表示
1.1.1. 图
Graph= {V,E }
1.1.2. 点集
V= {v0,v1,v2,v3 }
1.1.3. 边集
有向图和无向图的区别还是相当大的
a. 无向边集
E= {(v0,v1),(v1,v2),(v1,v3),(v2,v3) }
b. 有向边集
E= {<v0,v1>,<v1,v2>,<v2,v1>,<v1,v3>,<v2,v3>,<v3,v2> }
1.2. 图的连通性
这块应该都有所了解,此处从略。
2. 图的数据结构
我知道前两部分的数据结构很拉,但谁不是从基础开始学起的呢。
2.1. 邻接矩阵
顾名思义,就是使用矩阵来存储。值得注意的是,无向图的矩阵是关于对角线对称的。
2.1.1. 图示
下面的矩阵表示从第 i 行到第 j 列的路径长度
∞∞∞∞∞2∞∞∞73∞∞4∞∞5∞∞∞∞∞61∞
2.1.2. 数据结构
很明显,如果结点数为 n,邻接矩阵的数据结构就是用 n×n 的二维矩阵来表示的,这种数据结构适用于节点数量较少的稠密矩阵。此处没有路径直接用 0 来表示。
2.2. 邻接链表
2.2.1. 图示
2.2.2. 数据结构
可以看到,有一系列头节点,以及一系列跟随节点。头节点后面跟着的,就是从它直接可达的节点。这种数据结构适用于边数较少的稀疏图。
由于 python 没有指针,所以还是用 C++ 写吧
2.3. 链式前向星(用数组模拟邻接链表)
来自某些网站的介绍
前向星是一种特殊的边集数组,我们把边集数组中的每一条边按照起点从小到大排序,如果起点相同就按照终点从小到大排序,并记录下以某个点为起点的所有边在数组中的起始位置和存储长度,那么前向星就构造好了。
再把上面的图拿下来。
2.3.1. 初步构建
此时,我们构造这样的数据结构
同时,加边的方式也变了很多
乍一看,是个人都很难看懂,所以需要一番仔细分析。
在结构体 Edge 中,理所应当的存储的就是边的信息,而 head 数组,就是为辅助它而生的。我们先尝试按照代码逻辑进行加边的操作。(在本例中,其实还不怎么能看得出来)
cnt | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|
dist | 2 | 5 | 3 | 6 | 4 | 1 | 7 |
from | 1 | 2 | 1 | 3 | 4 | 4 | 5 |
to | 2 | 4 | 3 | 5 | 3 | 5 | 2 |
next | -1 | -1 | 1 | -1 | -1 | 5 | -1 |
head[from] | 1 3 | 2 | 3 | 4 | 5 6 | 6 | 7 |
在取出边的时候是怎么取的呢
控制台输出
2.3.2. 根据代码与流程进行分析
外层循环表示有多少个节点,也就是说,我们还是从节点出发,然后再判断这个节点的出度为多少。而出度的判断,则需要用到 next 了。
首先,在这里的示例中,head[i]
的值与节点的编号看似是相同的,但其实可以发现,每当遇到一个来源相同的节点,head 在此处的值都会被覆盖一次。也就是说,head[from]
中会记录以 from 为起点的边的最大索引。
另外,这里还有一个被忽略的点,即在建图完成之后,只有索引值最低的 head 元素是有效的,也就是说,上表中 from 值相同的 head 元素,在后续操作时,只有“第一个”head 是有用的,在本例中其实有点看不出来,在节点和边较多的图中能体现的比较明显
而在循环中的 t = e[t].next
这一步极为关键,上次看到这个式子还是在 KMP 算法中。当时,j = next[j]
的意思是,将当前不匹配的索引前向丢弃(这一块有所遗忘,可能还需要复习一下)。在这里,e[cnt].next = head[from]
的 next 中记录了上一次遇到的,以 from 为起始节点的那条边的索引。
另外,光是看这么一个式子,即 t = e[t].next
,其实就有链表的一重意思在里面了,但还不完全是链表。
现在再来看内层的循环,根据上面关于 head 的讨论,t=head[i]
这句的含义为,取出以 i 为起始节点的索引值最大的一条边(即起点为 i 的,最后一次输入的边)。因此,在输出过程中,会将最后一条以 i 为起始的边先输出出来。
而后进行的 t = e[t].next
操作,即取出同样以 i 为起始的倒数第二条边。因此,next 其实不如说是 forward,也就是说,这种取出的方式,其实是按照输入的反向进行输出的。最后,当 t 的值为 -1 时,也就是说,以 i 为起始的节点没有边了,那么就停止。
至此,我个人对于链式前向星这一存储图的数据结构的第一次系统整理,让我对数据结构有了一番新的认识。
2.3.3. 使用样例
我们不妨就拿这种数据结构尝试写一下拓扑排序吧。
2.4. multi_map 存储
在 map 数据结构中,它不允许有相同的键,但是 multi_map 却能够重复的添加相同的键。因此,我们可以使用它来存储图的结构,其 first 和 second 分别对应一条边的起点和终点。