并查集:
- 将两个集合合并
- 询问某个元素是否在一个集合之中
并查集可在近乎O(1)内完成这两个操作
基本思想: 每一个集合用一个树的形式来维护,每个树的根节点的编号代表了这个树,然后每个节点记录他的父节点P[x]
判断是否是根节点: if(p[x]==x)
如何求x的集合编号: while(p[x] != x) x = p[x];
如何合并两个集合: 就是把一个集合当做另一个集合的儿子
假设px是集合x的编号, py是集合y的编号,让px = y 把两个集合合并,就是集合x当做集合y的孩子了
优化(路径压缩): 当我从某个节点向上找到根节点的话,我就把这个路径上,所有的节点,全部父节点指向根节点
例题:合并集合
#include<iostream>
using namespace std;
const int N = 100010;
int p[N];
//根节点有p[x]=x的性质
int find(int a) //寻找a号节点所在树 + 路径压缩的优化一起在find里面完成
{
if(p[a]!=a) p[a] = find(p[a]);
return p[a];
}
int main()
{
int n,m;
cin >> n >> m;
for(int i=1;i<=n;i++) p[i] = i;
while(m--)
{
int a,b;
char op;
cin >> op >> a >> b;
if(op=='M')
{
if(find(a)!=find(b)){
p[find(a)] = find(b);
}
}
if(op=='Q'){
if(find(a)==find(b)) puts("Yes");
else puts("No");
}
}
return 0;
}
例题:连通块中点的数量
#include<iostream>
using namespace std;
const int N = 100010;
int p[N]; //代表i号节点所在的那个树的根节点是哪一个
int the_size[N]; // 代表i号节点所在的那个树(也就是所在的连通块)的点数的大小
int find(int a)
{
if(p[a]!=a) //注意千万不要把递归写到一个循环里,别写成while
{
p[a] = find(p[a]);
}
return p[a];
}
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
p[i] = i;
the_size[i] = 1;
}
while(m--)
{
string op;
cin >> op;
int a,b;
if(op=="C")
{
int a,b;
cin >> a >> b;
int p_a = find(a);
int p_b = find(b);
if(p_a!=p_b){
p[p_a] = p_b;
the_size[p_b] += the_size[p_a];
}
}
if(op=="Q1"){
cin >> a>> b;
if(find(a)==find(b)) puts("Yes");
else puts("No");
}
if(op=="Q2")
{
cin >> a;
cout << the_size[find(a)]<<endl;
}
}
return 0;
}
例题: PAT1021
给了n个节点,n-1条边,只要他是一个连通块,就是个树
但是如果是多个连通块,就不是树了
换言之,一个n个节点的树一定有n-1个边
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
const int N = 10010, M = N*2;
int head[N],e[M],ne[M],idx;
int p[N];
int n;
int find(int num)
{
if(p[num]!=num) p[num] = find(p[num]);
return p[num];
}
int add(int a,int b)
{
e[idx] = b;
ne[idx] = head[a];
head[a] = idx;
idx++;
}
int dfs(int now_node,int father)
{
int depth = 1;
for(int i=head[now_node];i!=-1;i=ne[i])
{
int j = e[i];
if(j==father) continue;
depth = max(depth,dfs(j,now_node)+1);
}
return depth;
}
int main()
{
cin >> n;
memset(head,-1,sizeof head);
for(int i=1;i<=n;i++) p[i] = i;
int cnt = n; //表示连通块的数目
for(int i=0;i<n-1;i++)
{
int a,b;
cin >> a >> b;
if(find(a)!=find(b))
{
cnt--;
p[find(a)] = find(b);
}
add(a,b),add(b,a);
}
if(cnt > 1) printf("Error: %d components",cnt);
else{
vector<int> nodes;
int the_max = -1;
for(int i=1;i<=n;i++)
{
int max_depth = dfs(i,-1);
if(max_depth>the_max){
nodes.clear();
nodes.push_back(i);
the_max = max_depth;
}
else if(max_depth==the_max){
nodes.push_back(i);
}
}
for(int i=0;i<nodes.size();i++) cout<<nodes[i]<<endl;
}
return 0;
}