并查集
并查集通常用来解决连通性问题,在我们需要判断两个元素是否在同一个集合中时,需要想到使用并查集。
并查集主要有两个功能:
1.将两个元素添加到一个集合中。
2.判断两个元素在不在同一个集合
并查集原理:以三个元素为例,将三个元素A,B,C放在同一个集合中,相当于将三个元素连通在一起,使用一个一维数组father,若father[A] = B, father[B] = C,能够构成这样的等式即说明A、B、C连通(有向连通图)。由于我们只需要判断三个元素是否在同一个集合中,所以只有这样的单项连通就足够了,即A->B->C。
若A、B在同一个集合中,则顺着路径向下遍历的根一定是相同的。上方案例中A、B的根都为C。
//并查集里寻根的过程
int find(int u){
if(u == father[u]) return u;//若根为自己,则返回
else return find(father U);//若根非自己,根据数组递归
}
有相同根的元素在同一个集合中。
对并查集的初始话要使father[i] = i。表示i之间没有联系。
判断两个元素是否在同一个集合中,即判断根是否相同。
//初始化
void inti(){
for(int i = 0; i < father.size(); i ++){
father[i] = i;
}
}
//判断是否在同一集合
bool isSame(int i, int j){
u = find(u);
v = find(v);
return u == v;
}
路径压缩
并查集的判断中需要做递归,当对一棵深度较深的二叉树做递归时,往往会造成很多空间和时间的浪费。而我们只需要知道节点是否在同一个根之下。所以可将深度较深的二叉树改造成一个根节点,其余皆为其孩子的多叉树,深度为2。主要做法如下。
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) {
return u == father[u] ? u : 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;
}
模板里显示并查集的主要三个功能
- 寻找根的节点
- 将两个节点接入到同一个集合
- 判断两个节点是否在同一个集合
还有一种模板是按秩合并,将两个集合合并,在不使用路径压缩的情况下,将秩较小的树合入秩较大的树中,降低在树上查询的路径长度。
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;
if (rank[u] == rank[v] && u != v) rank[v]++; // 如果两棵树高度相同,则v的高度+1,因为上面 if (rank[u] <= rank[v]) father[u] = v; 注意是 <=
}
路径压缩版并查集
空间复杂度: O(n) ,申请一个father数组。
路径压缩后的并查集时间复杂度在O(logn)与O(1)之间,且随着查询或者合并操作的增加,时间复杂度会越来越趋于O(1)。
寻找存在的路径
就是上述并查集的一个基本实现,代码如下。
#include<iostream>
#include<vector>
using namespace std;
// 查找操作,用于找到元素n的根节点
int find(const vector<int>& father, int n) {
if (father[n] == n) // 如果n是其自身的父亲,则n是根节点
return n;
else // 否则,递归地查找n的父亲的根节点
return find(father, father[n]);
}
// 合并操作,用于将元素u和v所在的集合合并
void join(vector<int>& father, int u, int v) {
int n = find(father, u); // 找到u的根节点
int m = find(father, v); // 找到v的根节点
if (n == m) // 如果u和v已经在同一个集合中,则不需要合并
return;
else // 将v的根节点m的父亲设置为u的根节点n,实现合并
father[m] = n;
}
// 判断两个元素是否在同一个集合中
bool isSame(const vector<int>& father, int u, int v) {
int n = find(father, u); // 找到u的根节点
int m = find(father, v); // 找到v的根节点
return n == m; // 如果根节点相同,则u和v在同一个集合中
}
int main() {
int N, M; // N表示节点数量,M表示关系的数量
cin >> N >> M; // 输入节点数量和关系数量
vector<int> grid(N+1, 0); // 创建一个大小为N+1的向量,用于存储每个节点的父亲信息
for (int i = 0; i <= N; i++) { // 初始化每个节点的父亲为自身
grid[i] = i;
}
int resource, dest; // 定义两个变量,用于存储输入的节点对
for (int i = 0; i < M; i++) { // 循环M次,处理每对节点的关系
cin >> resource >> dest; // 输入一对节点
join(grid, resource, dest); // 将这对节点所在的集合合并
}
cin >> resource >> dest; // 输入需要查询的两个节点
if (isSame(grid, resource, dest)) // 判断这两个节点是否在同一个集合中
cout << 1 << endl; // 如果在同一个集合中,输出1
else
cout << 0 << endl; // 如果不在同一个集合中,输出0
return 0;
}
算法的空间复杂度为O(n),时间复杂度为O(1)。
251



被折叠的 条评论
为什么被折叠?



