并查集是一种维护集合的数据结构,它的名字中“并“ ”查“ ”集”分别取自Union(合并)、find(查找)、Set(集合)这3个单词。也就是说,并查集支持下面两个操作:
(1)合并:合并集合。
(2)查找:判定两个元素是否在一个集合。
那么并查集怎么定义呢?其实就是一个数组:
int father[N];
其中father[i]就表示元素i的父结点,而父节点本身也是集合中的元素。例如father[1]=2表示元素1的父结点是元素2,另外如果father[i]=i,说明元素i是该集合的根节点,但对同一个集合来说只存在一个跟结点,且将其作为所示集合的标识。
如下图,father[1]==1说明元素1的父结点就是自己,即元素1就是集合的根结点。father[2]==1说明元素2的父结点是元素1,father[3]=2和father[4]=2说明元素3和元素4的父结点都是元素2,这样元素1、2、3、4就在同一个集合中。father[5]=5和father[6]=5说明5和6是以5为根结点的另一集合。这样就得到了两个不同的集合。
通常,并查集有以下的基本操作。
(1)初始化
开始时,每一个元素都独立的一个集合,因此所有的father[i]=i
for(int i=0;i<n;i++){
father[i]=i;
}
(2)查找
查找的过程就是寻找根根结点的过程
//获得元素x所在集合的根结点
int findFather(int x){
while(x!=father[x]){//如果自己不是根结点
x=father[x];//获得自己的父结点
}
return x;
}
(3)合并
合并是指把两个集合合并成一个集合,题目一般会给出两个元素,要求把这个两个元素所在的集合合并。 具体实现上需要判断两个元素是否属于同一个集合,只有当两个元素属于不同集合时才能合并,而合并的过程一般是把其中一个集合的根结点的父亲指向另一个集合的根结点。
其实现代码如下:
void unionSet(int a,int b){
int faA=findFather(a);
int faB=findFather(b);
if(faA!=faB){ //如果不a和b不属于同一集合
father[faA]=faB; //合并
}
}
下面再介绍下路径压缩,其是用来优化并查集的查找性能。如下图,总共有10^5个元素形成一条链,对每个元素来说,插找根元素都需要一次一次向前查询,效果是非常差的。
因为查找只是为了找到根节点,可以把当前查询结点的路径上的所有父结点都指向跟结点。下一次查询的时候,就不需要一步一步往前回溯了。
其代码实现如下:
int findFather2(int x){
int a=x; //x会被覆盖,保存一份x的副本
while(x!=father[x]){//查找x的根结点
x=father[x];
}
//此时x已经为根结点,把路径上的所有结点的father都指向根结点
while(a!=father[x]){//查找x的根结点
int z=a;//a会被覆盖,先保存a的值
a=father[a];//a回溯父结点
father[z]=x;//将原先结点a的父亲改为跟结点
}
return x;
}