并查集简介:
- 维护同一种情况的集合。
一、基础版并查集:
基本步骤:初始化、不断合并的过程中查找、统计集合。
树状图:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1005;
int s[maxn];
void init_set(int n)//初始化
{
for (int i = 1; i <= n; i++)
s[i] = i;
}
int find_set(int x)//查找根节点
{
return x==s[x]?x:find_set(s[x]);
}
void union_set(int x,int y)//合并集合
{
x=find_set(s[x]);
y=find_set(s[y]);
if(x!=y)
s[x]=s[y];
}
int main()
{
ios::sync_with_stdio(false);
int n, m, x, y;
while (cin >> n >> m && n)
{
int ans = 0;
init_set(n);
for (int i = 1; i <= m; i++)
{
cin >> x >> y;
union_set(x,y);
}
for(int i=1;i<=n;i++)//有几种不同的根
if(i==s[i])
ans++;
cout << ans << endl;
}
return 0;
}
由于查找find_set()、合并union_set()正如上图一样,搜索深度为O(n),n为树的高度。可以分别对查找与合并进行优化(树的高度)。
二、合并的优化
步骤:
- 在合并x,y的时候先搜索到他们的根节点,并合并这两个根节点。
- 根据这两个根节点的高度不同,把高度小的加到高度大的集合上。
利用数组int height[]
来记录高度
const int maxn = 1005;
int s[maxn], height[maxn];
void init_set()
{
for (int i = 1; i <= maxn; i++)
{
s[i] = i;
height[i] = 1; //每棵树的高度
}
}
void union_set(int x, int y)
{
x = find_set(x);
y = find_set(y);
if (height[x] == height[y])
{
height[x]++; //将y合并到x上,树的高度+1;
s[y] = x;
}
else
{
if (height[x] > height[y]) //将较小的树加到较大的树上
s[y] = x;
else
s[x] = y;
}
}
三、查询的优化–路径压缩
树状图:
步骤:
搜索的过程中,改变根节点。
优点:
优化了下一次的查询,也优化了合并。
1.递归版
int find_set(int x)
{
if (x != s[x])
s[x] = find_set(s[x]);//路径压缩
return s[x];
}
2.非递归版:(数据过大,爆栈时应用)
int find_set(int x)
{
int r = x;
while (s[r] != x) //找根节点,并赋值给r
r = s[r];
int i = x, tmp;
while (i != r)
{
tmp = s[i];
s[i] = r; //将路径上的元素改为根节点
i = tmp;
}
}