一、图的基础知识
图是一种非线性数据结构,由「顶点 Vertex」和「边 Edge」组成。
1.图的类型:
根据边是否具有方向可以分为有向图,无向图
根据所有顶点是否连通可以分为连通图(对于连通图,从某个顶点出发,可以到达其余任意顶点),非连通图
2.图常用术语:
- 「邻接 Adjacency」:当两顶点之间存在边相连时,称这两顶点“邻接”。在
- 「路径 Path」:从顶点 A 到顶点 B 经过的边构成的序列被称为从 A 到 B 的“路径”。
- 「度 Degree」表示一个顶点拥有的边数。对于有向图,「入度 In-Degree」表示有多少条边指向该顶点,「出度 Out-Degree」表示有多少条边从该顶点指出。
3.图的表示:
图的常用表示方法包括「邻接矩阵」和「邻接表」。
对于邻接矩阵:A[i][j]表示i到j的边,1是有边,0是没有边。
- 对于无向图,两个方向的边等价,此时邻接矩阵关于主对角线对称。
- 将邻接矩阵的元素从1,0替换为权重,则可表示有权图。
使用邻接矩阵表示图时,我们可以直接访问矩阵元素以获取边,因此增删查操作的效率很高,时间复杂度均为O(1),然而,矩阵的空间复杂度为O(n²)
对于邻接表:使用n个链表来表示图,链表节点表示顶点。第 i条链表对应顶点i,其中存储了该顶点的所有邻接顶点(即与该顶点相连的顶点)。邻接表仅存储实际存在的边,而边的总数通常远小于 \(n^2\) ,因此它更加节省空间。然而,在邻接表中需要通过遍历链表来查找边,因此其时间效率不如邻接矩阵。邻接表结构与哈希表中的「链地址法」非常相似,因此我们也可以采用类似方法来优化效率。例如,当链表较长时,可以将链表转化为 AVL 树或红黑树,从而将时间效率从O(n)优化至 O(log n) ,还可以通过中序遍历获取有序序列;此外,还可以将链表转换为哈希表,将时间复杂度降低至 O(1) 。
二、图的基础操作
图的基础操作可分为对「边」的操作和对「顶点」的操作。
1.基于邻接矩阵的实现:
- 添加或删除边:直接在邻接矩阵中修改指定的边即可,使用O(1) 时间。而由于是无向图,因此需要同时更新两个方向的边。
- 添加顶点:在邻接矩阵的尾部添加一行一列,并全部填 0 即可,使用 O(n) 时间。
- 删除顶点:在邻接矩阵中删除一行一列。当删除首行首列时达到最差情况,需要将 (n-1)^2 个元素“向左上移动”,从而使用 O(n^2) 时间。
- 初始化:传入 n 个顶点,初始化长度为 n 的顶点列表
vertices
,使用 \O(n) 时间;初始化 n * n 大小的邻接矩阵adjMat
,使用 O(n^2) 时间。
2.基于邻接表的实现
设无向图的顶点总数为 n 、边总数为 m ,则有:
- 添加边:在顶点对应链表的末尾添加边即可,使用 O(1) 时间。因为是无向图,所以需要同时添加两个方向的边。
- 删除边:在顶点对应链表中查找并删除指定边,使用 O(m) 时间。在无向图中,需要同时删除两个方向的边。
- 添加顶点:在邻接表中添加一个链表,并将新增顶点作为链表头节点,使用 O(1) 时间。
- 删除顶点:需遍历整个邻接表,删除包含指定顶点的所有边,使用 O(n + m) 时间。
- 初始化:在邻接表中创建 n 个顶点和 2m 条边,使用 O(n + m) 时间。
三、图的遍历
「图」和「树」都是非线性数据结构,都需要使用「搜索算法」来实现遍历操作。
与树类似,图的遍历方式也可分为两种,即「广度优先遍历 Breadth-First Traversal」和「深度优先遍历 Depth-First Traversal」,也称为「广度优先搜索 Breadth-First Search」和「深度优先搜索 Depth-First Search」,简称 BFS 和 DFS。
1.BFS(通常借助队列来实现)
从某个顶点出发,先遍历该顶点的所有邻接顶点,然后遍历下一个顶点的所有邻接顶点,以此类推,直至所有顶点访问完毕。
遍历一次所有节点,遍历两次(因为是无向图)所有边(因为要通过边到达另一个节点),总时间为O(V+E),空间为O(V)
2.DFS
从某个顶点出发,访问当前顶点的某个邻接顶点,直到走到尽头时返回,再继续走到尽头并返回,以此类推,直至所有顶点遍历完成。
总时间为O(V+E),空间为O(V)