Appearance
图论基础
图的逻辑结构和具体实现
有向无权图
一幅图是由节点和边构成的
- 根据逻辑结构,可以将图的结点定义为:
/* 图节点的逻辑结构 */
class Vertex {
int id;
Vertex[] neighbors;
}
- 上面的这种实现是「逻辑上的」,实际上我们很少用这个
Vertex
类实现图,而是用常说的邻接表和邻接矩阵来实现。
// 邻接表
// graph[x] 存储 x 的所有邻居节点
List<Integer>[] graph;
// 邻接矩阵
// matrix[x][y] 记录 x 是否有一条指向 y 的边
boolean[][] matrix;
邻接表,好处是占用的空间少,当无法快速判断两个结点是否相邻。
邻接矩阵可以直接判断,效率高。
图论中特有的度(degree)的概念,在无向图中,「度」就是每个节点相连的边的条数。由于有向图的边有方向,所以有向图中每个节点「度」被细分为入度(indegree)和出度(outdegree)
加权有向图
// 邻接表
// graph[x] 存储 x 的所有邻居节点以及对应的权重
List<int[]>[] graph;
// 邻接矩阵
// matrix[x][y] 记录 x 指向 y 的边的权重,0 表示不相邻
int[][] matrix;
无向图
也很简单,所谓的「无向」,是不是等同于「双向」?如果连接无向图中的节点 x
和 y
,把 matrix[x][y]
和 matrix[y][x]
都变成 true
不就行了;邻接表也是类似的操作,在 x
的邻居列表里添加 y
,同时在 y
的邻居列表里添加 x
。
图的遍历
// 记录被遍历过的节点
boolean[] visited;
// 记录从起点到当前节点的路径
boolean[] onPath;
/* 图遍历框架 */
void traverse(Graph graph, int s) {
if (visited[s]) return;
// 经过节点 s,标记为已遍历
visited[s] = true;
// 做选择:标记节点 s 在路径上
onPath[s] = true;
for (int neighbor : graph.neighbors(s)) {
traverse(graph, neighbor);
}
// 撤销选择:节点 s 离开路径
onPath[s] = false;
}
如果图不包含环,则参考多叉树的遍历:
/* 多叉树遍历框架 */
void traverse(TreeNode root) {
if (root == null) return;
for (TreeNode child : root.children) {
traverse(child);
}
}