- 深度优先搜索 (Depth-First Search)
- 广度优先搜索 (Breadth-First Search)
💡 二叉树的递归遍历其实就是 DFS,而迭代遍历就是 BFS。
void dfs(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本节点所连接的其他节点) {
处理节点;
dfs(图,选择的节点); // 递归
回溯,撤销处理结果
}
}
并查集常用来解决连通性问题。需要判断两个元素是否在同一个集合里的时候,就可以用并查集。 两个主要功能:
- 将两个元素添加到一个集合中;
- 判断两个元素在不在同一个集合。
将三个元素 A, B, C(分别是数字)放在同一个集合,其实就是将三个元素连通在一起。
只需要用一个一维数组表示,即:father[A] = B
, father[B] = C
。
这样就表示 A 与 B 与 C 连通了(有向连通图)。
// 将v,u 这条边加入并查集
void join(int u, int v) {
u = find(u); // 寻找u的根
v = find(v); // 寻找v的根
if (u == v) return; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回
father[v] = u;
}
// 并查集里寻根的过程
int find(int u) {
if (u == father[u])
return u; // 如果根就是自己,直接返回
else
return find(father[u]); // 如果根不是自己,就根据数组下标一层一层向下找
}
要表示 C 也在同一个集合里,需要 father[C] = C
,即 C 的根也为 C,这样就表示 A, B, C
都在同一个集合里了。
// 并查集初始化,默认指向自己
void init() {
for (int i = 0; i < n; ++i) {
father[i] = i;
}
}
// 判断 u 和 v是否找到同一个根
bool isSame(int u, int v) {
u = find(u);
v = find(v);
return u == v;
}
find
搜索过程像是在一个“多叉树”中从叶子反向搜索到根结点。
如果这棵多叉树高度很深的话,find
的寻跟过程会递归很多次。
由于查找的最终目的是只要知道这些结点在同一个根下就可以,所以这棵“多叉树”只需要构造成高度为 2。 即所有叶子结点指向根结点。
将 father
数组构造成“多叉树”的过程就叫路径压缩。
// 并查集里寻根的过程
int find(int u) {
if (u == father[u])
return u;
else
return father[u] = find(father[u]); // 路径压缩
}
int n = 1005; // n根据题目中节点数量而定,一般比节点数量大一点就好
vector<int> father = vector<int> (n, 0);
// 并查集初始化
void init() {
for (int i = 0; i < n; ++i) {
father[i] = i;
}
}
// 并查集里寻根的过程
int find(int u) {
if (u == father[u])
return u;
else
return father[u] = find(father[u]); // 路径压缩
}
// 判断 u 和 v是否找到同一个根
bool isSame(int u, int v) {
u = find(u);
v = find(v);
return u == v;
}
// 将v->u 这条边加入并查集
void join(int u, int v) {
u = find(u); // 寻找u的根
v = find(v); // 寻找v的根
if (u == v)
return ; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回
father[v] = u;
}
rank 表示树的高度,即树中结点层次的最大值。
当有两个集合,即两个多叉树需要合并时,将高度小的树合并入高度大的树,使合成后的树高度最小。
int n = 1005; // n根据题目中节点数量而定,一般比节点数量大一点就好
vector<int> father = vector<int> (n, 0); // C++里的一种数组结构
vector<int> rank = vector<int> (n, 1); // 初始每棵树的高度都为1
// 并查集初始化
void init() {
for (int i = 0; i < n; ++i) {
father[i] = i;
rank[i] = 1; // 也可以不写
}
}
// 并查集里寻根的过程
int find(int u) {
return u == father[u] ? u : find(father[u]);// 注意这里不做路径压缩
}
// 判断 u 和 v是否找到同一个根
bool isSame(int u, int v) {
u = find(u);
v = find(v);
return u == v;
}
// 将v->u 这条边加入并查集
void join(int u, int v) {
u = find(u); // 寻找u的根
v = find(v); // 寻找v的根
if (rank[u] <= rank[v]) father[u] = v; // rank小的树合入到rank大的树
else father[v] = u;
// 如果两棵树高度相同,则 v 的高度 +1
// if (rank[u] <= rank[v]) father[u] = v;
if (rank[u] == rank[v] && u != v) rank[v]++;
}
- 空间复杂度:
O(n)
,申请一个father
数组。 - 时间复杂度: 路径压缩后的并查集时间复杂度在
O(logn)
与O(1)
之间,且随着查询或者合并操作的增加,时间复杂度会越来越趋于O(1)
。
在第一次查询的时候,相当于 n
叉树上从叶子节点到根节点的查询过程,时间复杂度是 logn
。
但路径压缩后,后面的查询操作都是 O(1)
,而 join
函数和 isSame
函数里涉及的查询操作也是一样的过程。