并查集用于快速的分类和查找,处理一些不相交集合的合并与查询
基本的操作:
1. 初始化,把每个值对应的集合都设为自己
2. 合并,把一个集合合并到另一个上
3. 查询,本博客采用路径压缩算法实现查询的操作,查找元素在集合中对应的集合位数
/*
基本的操作:
1.初始化,把每个值对应的集合都设为自己
2.合并,把一个集合合并到另一个上
3.查询,本博客采用路径压缩算法实现查询的操作,查找元素在集合中对应的集合位数
*/
#include <iostream>
using namespace std;
#define max 1000000
int parent[max]; //定义一个parent数组,parent[i]表示i元素所对应的集合为parent[i]
int find( int p ) //路径压缩的find算法
{
if( parent[p] != p ) return parent[p] = find( parent[p] ); //如果根不是自己,那么就递归调用find函数找到根并连接
return parent[p]; //否则直接返回
}
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++ )
{
parent[i] = i; //初始化
}
int m;
cin >> m;
for (int i = 1; i <= m; i++)
{
int x,y;
cin >> x >> y;
int rootx = find(x);
int rooty = find(y);
parent[rootx] = rooty; //由于进行了路径压缩,所以直接随意的把一个集合接到另一个上
}
return 0;
}
复杂度:N次合并M查找的时间复杂度为O(M Alpha(N)),这里的Alpha(n)可以视为不超过4的数,所以可以视并查集的操作为有系数的线性时间。
并查集的启发式合并:
由于路径压缩改变了树的结构,所以在可撤销并查集和可持久化并查集时就不大好用了。所以采用启发式合并有时就会有优势。
并查集的启发式合并就是在两个集合合并的时候,将siz小的连向siz大的。复杂度为O(nlogn)。
主要的复杂度在于找到根节点。我们考虑一次合并,将siz小的连向大的,这时候只有siz小的那棵子树在以后的查询中多跑一次(因为祖先节点多了一个),我们考虑一个节点最深在哪里,那就是不断的作为siz小的那一部分合并,那这时候每次深度加1,总大小至少翻倍,所以最深log层。