并查集
- 定义
定义引用百度百科:
并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。
简单的说将数据按共有的特性分类,每一个分类为一个树。一个个分类树组成的森林就是并查集,拥有合并(merge)操作和查找操作(find),合并是将新的数据加入到并查集,查找是找到指定数据的根节点。
并查集的一般用途就是用来维护某种具有自反、对称、传递性质的关系的等价类。
- 数据结构
简单的并查集运用一个数组来存储每个数据的父节点,初始值为-1或者数组下标的时候表示该节点为根节点。
vector<int>parent
例如:
parent | Value |
---|---|
0 | -1 |
1 | -1 |
2 | -1 |
parent | Value |
---|---|
0 | 0 |
1 | 1 |
2 | 2 |
如果用-1来表示该节点为根节点,需要在合并的时候判断树根节点是否相同,不然会在find函数中出现死循环。
例如: 3的根节点为0,而6的根节点也是0
合并后会出现0的根节点为0(应该为-1),然而在find函数中递归寻找0的根节点,直到找到的parent值为-1才停下,这样程序会一直在find函数中循环。
如果用i来初始化,0的根节点为0,表示0自己为根,find递归会直接结束。
(注:合并的实质是将根节点成为另一个树根节点的子节点)
- 具体实现
class ufs {
public:
ufs(int n) :
parent(vector<int>(n, -1)),
count(0)
{
//初始化一个parent数组全部值为0
//也可以用i初始化
//for(int i=0;i<n;i++)
// parent[i]=i
}
//查找根节点用于merge函数合并
int find(int value) {
if (parent[value] == -1) {
return value;
}
return find(parent[value]);
}
void merge(int x, int y) {
int find_x = find(x);//找到x的根节点
int find_y = find(y);//找到y的根节点
//避免根节点相同的情况 find死循环
//如果使用i来初始化就不用这一步
if(find_x!=find_y)
parent[find_y] = find_x;
}
vector<int>parent;
};
- 优化
设立一个rank数组用来记录树的深度,每一次合并的时候都是深度较小的树成为深度较大的树的子节点。
优化后的代码:
class ufs {
public:
ufs(int n) :parent(vector<int>(n, -1)), count(0),rank(vector<int>(n)) {
/* for (int i = 0; i < n; i++)
parent[i] = i;*/
}
int find(int value) {
if (parent[value] == -1) {
return value;
}
return parent[value]=find(parent[value]);
}
void _union(int x, int y) {
int find_x = find(x);
int find_y = find(y);
if(find_x!=find_y){
if(rank[x]>rank[y]){
swap(find_x,find_y);//比较深度
}
parent[find_y] = find_x;//y对应的根节点成为x对应根节点的子节点
if(rank[x]==rank[y]){
rank[x]++;//如果深度相同,合并后x的深度会+1
}
}
}
vector<int>parent;
vector<int>rank;//记录深度
};
};