前言:图可以玩出更多的算法,解决更复杂的问题,但本质上图可以认为是多叉树的延伸。面试笔试很少出现图相关的问题,就算有,大多也是简单的遍历问题,基本上可以完全照搬多叉树的遍历。
一、图的具体实现
图的实现方法①:逻辑上的实现
/* 图节点的逻辑结构 */
class Vertex {
int id;
Vertex[] neighbors;
}
类似多叉树节点
/* 基本的 N 叉树节点 */
class TreeNode {
int val;
TreeNode[] children;
}
图本质上就是个高级点的多叉树,适用于树的 DFS/BFS 遍历算法,全部适用于图。
图的实现方法②:邻接表
实际使用中更常用的是邻接表
把每个节点 x 的邻居都存到一个列表里,然后把 x 和这个列表关联起来,这样就可以通过一个节点 x 找到它的所有相邻节点。
// 邻接表
// graph[x] 存储 x 的所有邻居节点
List<Integer>[] graph;
图的实现方法③:邻接矩阵
邻接矩阵则是一个二维布尔数组,我们权且称为 matrix,如果节点 x 和 y 是相连的,那么就把 matrix[x][y] 设为 true(上图中绿色的方格代表 true)。如果想找节点 x 的邻居,去扫一圈 matrix[x][…] 就行了。
// 邻接矩阵
// matrix[x][y] 记录 x 是否有一条指向 y 的边
boolean[][] matrix;
邻接表 VS 邻接矩阵:
邻接表,好处是占用的空间少。
邻接矩阵里面空着那么多位置,肯定需要更多的存储空间。
但是,邻接表无法快速判断两个节点是否相邻。
比如说我想判断节点 1 是否和节点 3 相邻,我要去邻接表里 1 对应的邻居列表里查找 3 是否存在。但对于邻接矩阵就简单了,只要看看 matrix[1][3] 就知道了,效率高。
所以说,使用哪一种方式实现图,要看具体情况。
二、图的遍历
遍历过程:
(我们需要引入两个数组,visited 数组用于处理图中的环,防止递归重复遍历同一个节点进入死循环;onPath 数组用于存储临时路径。
在 visited 中被标记为 true 的节点用灰色表示,在 onPath 中被标记为 true 的节点用绿色表示)
参考多叉树,多叉树的遍历框架如下:
/* 多叉树遍历框架 */
void traverse(TreeNode root