1.并查集
并查集是一种维护集合的数据结构
Union Find Set
支持下面两个操作:
1.合并:合并两个集合
2.查找:判断两个元素是否在一个集合
int father[N]; //father[i]表示i元素的父亲节点
father[i]=i说明元素i是该集合的根节点,对同一个几个来说只存在一个根节点,且将其作为所属集合的标识
2. 并查集的基本操作
1.初始化
每个元素都是一个独立的集合,father[i]=i;
for(int i=1;i<=N;i++){
father[i]=i
}
2.查找
规定同一个集合中只能存在一个根节点,查找操作就是寻找根节点的过程。实现方式是递推或者递归。
即反复寻找父亲节点即(father[i]==i)的节点
//递推
int findFather(int x){
while(x!=father[x]){
x=father[x];//不是根节点,获得自己的父亲节点
}
return x;
}
//递归
int findFather(int x){
if(x==father[x])return x;
else return findFather(father[x]);
}
3.合并
将两个集合合并成一个集合,题目中一般给出两个元素,要求把这两个元素所在的集合合并。
先判断两个元素是否属于同一个集合,只有当两个元素属于不同集合的时候才进行合并
合并的过程是将一个集合的根节点的父亲指向另一个集合的根节点
void Union(int a,int b){
int faA=findFather(a);
int faB=findFather(b);
if(faA!=faB){
father[faA]=faB;
}
}
在合并过程中,只对两个不同的集合进行合并,如果两个元素在相同的集合中,都不会对他们进行操作,这就保证了在同一个集合中一定不会产生环,即并查集产生的每一个集合都是一棵树。
4.路径压缩
在查询过程中把当前查询节点路径上所有节点的父亲都指向根节点,查找的时候就不用一直去回溯找父亲节点了,查找的复杂度可以降到O(1);
转化过程:
1.按原先的写法获得x的根节点r
2.重新开始走一边寻找根节点的过程,把路径上所有节点的父亲全部改为跟节点r
//递推
int findFather(int x){
int a=x;
while(x!=father[x]){
x=father[x];
}
while(a!=father[a]){
int z=a;
a=father[a];
father[z]=x;
}
return x;
}
//递归
int findFather(int v){
if(c==father[v])return v;
else{
int F=findFather(father[v]);
father[v]=F;
return F;
}
}
5.按秩合并
按秩合并的基本思想是使包含较少结点的树德根指向包含较多结点的树的根,而这个树的大小可以抽象为树的高度,即高度小的树合并到高度大的树,这样资源利用更加合理。
为了实现一个按秩合并的不想交集合森林,要记录下秩的变化。对于每个结点x,有一个整数rank[x],它是x的高度(从x到其某一个后代叶结点的最长路径上边的数目)的一个上界。(即树高)。当由MAKE-SET创建了一个单元集时,对应的树中结点的初始秩为0,每个FIND-SET操作不改变任何秩。当对两棵树应用UNION时,有两种情况,具体取决于根是否有相等的秩。当两个秩不相等时,我们使具有高秩的根成为具有较低秩的根的父结点,但秩本身保持不变。当两个秩相同时,任选一个根作为父结点,并增加其秩的值路径压缩。
void Union(int x, int y) {
int root_x = findParent(x);
int root_y = findParent(y);
if (root_x != root_y) {
if (rank[root_x] < rank[root_y]) {
swap(root_x, root_y);
}
parent[root_y] = root_x;//让高秩的跟作为低秩的跟的父节点
if (rank[root_x] == rank[root_y])rank[x] += 1;//如果两个秩相同时,任选一颗作为父节点,同时秩加一
}
}
一个完整的并查集模板:
class Djset {
public:
vector<int> parent;//记录父节点,所有父节点相同的节点位于同一连通图
vector<int> rank;//节点的秩,记录该节点位于树的深度,从子节点出发,用于优化,在合并两个父节点时,通过rank的大小判断谁父谁子
Djset(int n) {//初始化每个节点为一个联通分量,父节点为自己
for (int i = 0; i < n; i++) {
parent.push_back(i);
rank.push_back(0);
}
}
//路径压缩,遍历过程中所有父节点直接指向根节点。减少后续查找次数
int findParent(int x) {
if (x != parent[x]) {
parent[x] = findParent(parent[x]);
}
return parent[x];
}
//合并两个节点,如果处于同一个并查集,则不需要合并,如果不处于同一个并查集,判断两个rootx和rooty谁的秩大
void Union(int x, int y) {
int root_x = findParent(x);
int root_y = findParent(y);
if (root_x != root_y) {
if (rank[root_x] < rank[root_y]) {
swap(root_x, root_y);
}
parent[root_y] = root_x;//让高秩的跟作为低秩的跟的父节点
if (rank[root_x] == rank[root_y])rank[x] += 1;//如果两个秩相同时,任选一颗作为父节点,同时秩加一
}
}
};