并查集
1.并查集的定义
并查集是一种维护集合的数据结构,他的名字中”并”、”查”、”集”分别取自Union(合并)、Find(查找)、Set(集合),也就是说,并查集支持下面的两个操作:
- 合并:合并两个集合。
- 查找:判断两个集合是否在一个集合中。
并查集的实现可以通过一个数组:
int father[N]
其中father[i]表示元素 i 的父亲结点,而父亲结点本身也是这个集合中的元素,例如father[1] = 2就表示元素1的父亲结点时元素2,以这种父亲关系来表示元素所属的集合。另外,如果father[i] = i,则说明元素i是该集合的根结点,但对同一个集合来说只存在一个根结点,且将其作为所属集合的标识。
2.并查集的查找
1.初始化:
一开始每个元素都是独立的集合,因此需要令所有father[i] = i:
for(int i = 1;i <= N;i++){
father[i] = i; //令father[i] = -1也可
}
2.查找:
由于规定同一个集合只存在一个根结点,因此查找操作就是对给定的结点寻找其根结点的过程。实现的思路可以是反复寻找其父亲结点直到找到根结点(即 father[i] == i 的结点)。
int findFather(int x){
while(x != father[x]){ //如果不是根结点,继续循环
x = father[x]; //获得自己父亲结点
}
return x;
}
3.合并
合并是把两个集合合并成一个集合,题目中一般给出两个元素,要求把两个元素所属的集合合并。具体实现上一般是先判断两个元素是否属于同一个集合,只有当两个元素不属于同一个集合时才能合并,而合并的过程一般是把其中的根结点的父亲结点指向另一个集合的根结点。
主要步骤:
1. 对于给定的两个元素a、b,判断它们是否属于同一个集合,可以调用上面的查找函数,对这两个元素分别查找根结点,然后判断其根结点是否相同。
2. 合并两个集合:在 1 中已经获得了两个元素的根结点faA与faB,因此只需要把其中的一个父结点指向另一个父结点。例如可以令father[faA] = faB,当然反过来令father[faB] = faA,也是可以的,没有什么区别。
void Union(int a,int b){
int root1 = findFather(a); // 查找 a 的根结点
int root2 = findFather(b); // 查找 b 的根结点
if(root1 != root2) //如果不属于同一个集合
father[root2] = root1; //合并
}
3.路径压缩
如果题目给出的元素数目很多并且形成了一条链,那么这个查找函数的效率就会非常低。这个时候就可以优化查询函数,即把当前查询结点的路径上的所有结点的父亲都指向根结点,查找的时候就不需要一直回溯去找父亲了。
基本步骤:
1. 按照原先的写法获得 X 的根结点root。
2. 重新从 X 开始走一遍寻找根结点的过程,把路径上的所有结点的父亲全部改为根结点root。
int findFather(int x){
int a = x;
while(x != father[x]){ //如果不是根结点,继续循环
x = father[x]; //获得自己父亲结点
}
//到这里 x 里面存放的是根结点,下面吧路径上所有的结点的father都改成根结点
while(a != father[a]){
int z = a; //因为下面a要被覆盖,所以先保存一下
a = father[a]; //回溯父亲结点
father[z] = x; //将原先结点a的父亲改为根结点
}
return x;
}
示例代码:
#include <iostream>
#include <vector>
using namespace std;
const int maxn = 1000;
int father[maxn];
void init(){
for(int i = 0;i < maxn;i++)
father[i] = i;
father[2] = 1;
father[3] = 2;
father[4] = 2;
father[6] = 5;
}
int findFather(int x){
int a = x;
while(x != father[x]){ //如果不是根结点,继续循环
x = father[x]; //获得自己父亲结点
}
//到这里 x 里面存放的是根结点,下面吧路径上所有的结点的father都改成根结点
while(a != father[a]){
int z = a; //因为下面a要被覆盖,所以先保存一下
a = father[a]; //回溯父亲结点
father[z] = x; //将原先结点a的父亲改为根结点
}
return x;
}
void Union(int a,int b){
int root1 = findFather(a); // 查找 a 的根结点
int root2 = findFather(b); // 查找 b 的根结点
if(root1 != root2) //如果不属于同一个集合
father[root2] = root1; //合并
}
int main(void){
init();
cout<<"6 的根结点是 "<<findFather(6)<<endl;
Union(3,6);
cout<<"6 的根结点是 "<<findFather(6)<<endl;
return 0;
}