本章节内容使用 java 实现,Github 代码仓:https://github.com/ZhekaiLi/Code/tree/main/Graph/src
查看文章内的图片可能需要科学上网! 因为使用了github管理图片,因此如果出现无法加载的情况请翻墙
【参考资料】imooc 波波老师:玩转算法系列–图论精讲 面试升职必备(Java版)
【往期博客链接】
图论算法(1、2):图的分类、图的基本概念(无向图与有向图、无权图、无环图、完全图、二分图;简单图、连通分量、图的生成树、子图与母图)
图论算法(3):图的基本表示(邻接矩阵、邻接表、邻接矩阵与邻接表的对比)
图论算法(4):图的深度优先遍历 DFS
图论算法(5):图的广度优先遍历 BFS
图论算法(6):LeetCode 图论算法练习(785.判断二分图、695.岛屿的最大面积、Floodfill 算法、并查集)
3. 图的基本表示
3.1 邻接矩阵
图
G
=
(
V
,
E
)
G = (V, E)
G=(V,E) 的 邻接矩阵(adjacency matrix)
C
C
C 是如下定义的:
C
=
(
c
i
j
)
n
×
n
∈
{
0
,
1
}
n
×
n
c
i
j
=
{
1
,
(
i
,
j
)
∈
E
0
,
(
i
,
j
)
∉
E
\begin{aligned} C&=(c_{ij})_{n\times n}\in\{0,1\}^{n\times n}\\ c_{ij}&=\begin{cases} 1,\;\;(i,j)\in E\\ 0,\;\;(i,j)\notin E \end{cases} \end{aligned}
Ccij=(cij)n×n∈{0,1}n×n={1,(i,j)∈E0,(i,j)∈/E
而对于无向图来说,其邻接矩阵是对称的( c i j = c j i c_{ij}=c_{ji} cij=cji)
可以用下图中央这样的两列数据来表示一个无向图,然后将其翻译为邻接矩阵
java 实现:AdjMatrix.java
3.2 邻接表
为什么要引入邻接表?(邻接矩阵的复杂度)
邻接矩阵的的建图时间复杂度 O ( E ) O(E) O(E)(遍历每条边),以及判断两点是否相邻的时间复杂度 O ( 1 ) O(1) O(1) 没有提升空间,但其空间复杂度以及求相邻节点的时间复杂度较大
尤其是例如对于一个有3000个节点的树(无向图),它只有2999条边,即使是乘2也只有6000不到组数据,但是其对应的邻接矩阵的空间复杂度却是 O ( 300 0 2 ) O(3000^2) O(30002),近千倍的差距!
邻接表的定义
图的邻接表(adjacency list) 是所有节点的邻接表的集合
各节点的邻接表由其:
- 邻边(无向图)
- 出弧(有向图)
组成,并用一个单向链表列出,链表中每个单元对应于一条邻边/出弧,此外还可以包含弧上的权等作为数据域。
邻接表的表示
对于有向图 G = ( V , E ) G = (V, E) G=(V,E),一般用 A ( i ) A(i) A(i) 表示节点 i i i 的邻接表,即节点 i i i 的所有出弧构成的集合或链表
对于无向图, A ( i ) A(i) A(i) 则表示节点 i i i 邻边的集合
图的整个邻接表还可以用一个指针数组表示。例如:(下图中第一个指针数组表示, 1 → 2 1\to2 1→2 的权重为8, 1 → 3 1\to3 1→3 的权重为9)
java 实现:AdjList.java
邻接表的复杂度
相较于邻接矩阵的复杂度,邻接表在空间复杂度、求相邻节点的时间复杂度这两方面有明显优势,但其建图的时间复杂度、判断两点是否相邻的时间复杂度较大。
降低这两个复杂度的关键在于实现快速查看重复边/ 快速判断两点是否相邻,因此我们可以使用哈希表 O ( 1 ) O(1) O(1) 或红黑树 O ( log V ) O(\log V) O(logV) 代替链表(在 java 中分别对应 HashSet, TreeSet, LinkedList)
java 实现:Graph.java(使用红黑树)
3.3 比较:矩阵 vs 两种表
因此我门最终选用邻接表(TreeSet) 作为图的表达形式(当然也可以使用HashSet)