1.概念:
将编号分别为1…N的N个对象划分为不相交集合,
在每个集合中,选择其中某个元素代表所在集合。
常见两种操作:
合并两个集合
查找某元素属于哪个集合,判断两个元素是否属于同一个集合。
是一种树形的数据结构,又称“不相交集合”,用于处理一些不相交集合的合并及查询问题。
2.数学模型:
并查集的数学模型是若干不相交集合的集合S={A,B,C,...},它支持以下的运算: (1)INITIAL(A,x):构造一个取名为A的集合,它只包含一个元素x;
(2)MERGE(A,B):将集合A和B合并,其结果取名为A或B;
(3)FIND(x):找出元素x的所在集合,并返回该集合的名字。
3.算法实现:
1)用编号最小的元素标记每个集合;
set[x] 即元素x所在集合的最小元素
此时并查操作:
//查找任意x所在集合,而集合以最小元素为代表
int find(x){
return set[x];
}
时间复杂度:O(1);
//合并两个集合,即让代表元素大的集合的所有元素都小元素代表为代表
void Merge(a,b){
int i=min(set[a],set[b]);
int j=max(set[a],set[b]);
for(int k=1;k<=n;k++){
if(set[k]==j) set[k]=i;
}
}
int min(int a,int b){
return a>b?b:a;
}
int max(int a,int b){
return a>b?a:b;
}
时间复杂度O(n);
用树形结构改进
2)每个集合用一棵“有根树”表示
定义数组 fa[1..n] fa[i] = i , 则i表示本集合,并是集合对应树的根
fa[i] = j, j≠i, 则 j 是 i 的父节点.
查找:
int find(x){
while(father[x]!=x) x=father[x]; //找到根节点
return x;
}
合并:
void merge(int x,int y){
int fx=find(x),fy=find(y);
if(fx==fy) return;
fa[fx]=fy;
}
最坏情况 O(N);
再次改进,避免最坏情况。 //每次合并,都合并在一条线上。
3)路径压缩,引入秩合并集合
考虑每次查找时 修改路径上的所有节点,使其指向根节点。 //路径压缩
非递归:
int find(int x){
int r=x; //保存要查找节点
while(father[r]!=r) r=father[r]; //找到根节点
while(father[x]!=x){
int f=father[x]; //保存此节点的父节点
fa[x]=r; //更改父节点,使其指向根节点
x=f;
}
return r;
}
递归:
int find(int x){
if(x!=father[x]) father[x]=find(father[x]); //递归调用
return father[x];
}
合并两个集合时,为了避免树一直竖向增长,按秩合并。
增加一个数组,初始为0保存每个集合的秩
思路: 若两个集合秩相等,则任选一个根节点作为父节点,秩加1;
秩不同时,较小秩的集合指向较大秩的集合
void make_set(int x){ //初始化集合
father[x]=x; //根节点
R[x]=0; //秩大小,子树长度
}
void merge(int x,int y){
int tx=find(x),ty=find(y); //找到根节点,即所在集合
if(tx==ty) return; //在同一个集合
if(R[tx]<R[ty]{
father[tx]=ty;
}
else{
if(R[tx]==R[ty]) R[tx++];
father[ty]=tx;
}