并查集常用来解决连通性问题:
其实是一种n叉树的结构;
在图里: 比如我们要查两个节点是否连着,如果连着我们就可以用isSame找出来就好了;
大白话就是当我们需要判断两个元素是否在同一个集合里的时候,我们就要想到用并查集。
并查集主要有两个功能:
-
将两个元素添加到一个集合中。 join;
-
判断两个元素在不在同一个集合 isSame;
接下来围绕并查集的这两个功能来展开讲解。
//
并查集作为一种数据结构,我们要在原有基础上创建它,首先就需要建立一个数组father和一个代表树高度的rank数组,初始无值进入的时候自然是0高度;
vector<int> father(n);
vector<int> rank(n);
如果我们要把两个元素加入一个集合中(或者两个点连成一条线)
这里要注意一个问题,u和v会变成自己的根,然后在根相连;
// 将v,u 这条边加入并查集,v的根成u
void join(int u, int v) {
u = find(u); // 寻找u的根
v = find(v); // 寻找v的根
if (u == v) return; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回
father[v] = u;
}
节点的根本身先设置为自己,也就是init初始化
void init(int n){
for(int i=0;i<n;i++){
father[i]=i;
}
}
判断是不是在一个集合里(一张图里)
bool isSame(int u,int v){
u=find(u);//找u的根;
v=find(v);//找v的根;
return u==v;
};
寻根操作+路径压缩(把这条路径上的所有节点都指向根;比如1->2->3,作为树来看1是根节点,那么我们让3的根也为1,1->2,1->3);
//如果u是自己的根,那么就返回自己
//如果不是,那么u就逐渐指向根
int find(int u){
return u==father[u]?u:father[u]=find(father[u]);
}
rank操作是在merge里实现的:rank操作:如果两个点进行合并,如何判断谁是谁的爹是一个问题,我们认为,一个树的左右树高度差越小是进化,如果合并后的树左右子树高度差变大了就是退化,因此,我们才需要统计rank的必要;
void merge/join(int x,int y){
//找到根节点,因为都是根节点做连接操作;
x=find(x);
y=find(y);
//如果根节点相等,说明已经在一个树上了,那么就没必要操作了,高度也不会加;
if(x==y) return;
if(rank(x)>rank(y)) father[y]=x; //选择高度高的根为新的根节点,并且高度不会发生变化;
else{
if(rank(x)==rank(y)){
father[x]=y; //一样没有区别,就按照else的写法来
rank[y]++;
}
else father[x]=y; //高度不变;
}
return;
}
并查集构建方法2:设置结构体
注意,在结构体末尾直接加上一个名字就创建了这个名字的对象;
struct DisjointSet{
int father[MAX_N];
//初始化
DisjointSet(){
for(int i=0;i<MAX_N;i++){
father[i]=i;
}
}
int find(int u){
//这个逻辑处理错了就写错了,如果u是自己的根,就返回自己,如果不是,那么就返回他的爹,并且查询他的爹的根是不是自己,如果不是就往下走返回根节点;
return u==father[u]?u:father[u]=find(father[u]);
}
//就是join,
void join(int u,int v){
u=find(u);
v=find(v);
if(u==v) return;
father[u]=v;
}
void merge(int u,int v){
father[find(u)]=find(v);
}
bool isSame(int u,int v){
u=find(u);
v=find(v);
return u==v;
}
} ds;