一,首先是查找优化模板,如果数据不大直接手动合并,配合一下查找方式即可AC
int find(int x){
while(x != p[x]){
p[x] = p[p[x]];
x = p[x];
}
return x;
}
这种方式适合于将并查集各个节点的p值初始化为本身的情况,防止爆栈。
二,但节点数更多,并且需要记录各个集合的数据个数时,使用根节点负数存储优化,每一次合并到节点更深的分支上。
void init(){
for(int i = 1; i <= n;i++){
p[i] = -1;
}
}
int find(int x){ //数据打了会爆栈
if(p[x] < 0)return x;
else return p[x] = find(p[x]);
}
int find2(int x) {
while(p[x] > 0 && p[p[x]] > 0){
p[x] = p[p[x]];
x = p[x];
}
return x;
}
void union_(int a, int b){
int p1 = find(a);
int p2 = find(b);
if(p[p1] < p[p2]){ //合并,p1顶点更多(负数),因此合并到p2上,减少查找次数
p[p2] += p[p1];
p[p1] = p2;
}else{ //将p2合并到p1集合,减少查找次数。
p[p1] += p[p2];
p[p2] = p1;
}
}
这种方式节省空间的同时,也可以选择递归查找还是循环查找,find2防止爆栈。
每个根节点初始化为-1,代表这个集合中只有一个节点,为本身。如果一个节点不是这个集合的领导结点,那么他的p值为正,为她的领导结点。
例题超时代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n, m;
int p[30005];
void init(){
for(int i = 0; i <= n;i++){
p[i] = -1;
}
}
int find(int x) {
// if(p[x] < 0)return x;
// return p[x] = find(p[x]);
int r = x;
while(p[r] >= 0) //以负数头下标为 记录节点数时,需要其前驱下标为0合法的情况,只有为负数才是头节点
r = p[r];
int i = x, j;
while(p[i] >= 0 && p[i] != r){
j = p[i];
p[i] = r;
i = j;
}
return r;
}
void un(int a, int b){//合并
int p1 = find(a);
int p2 = find(b);
if(p[p1] < p[p2]){
p[p2] += p[p1];
p[p1] = p2;
}else{
p[p1] += p[p2];
p[p2] = p1;
}
}
int main(){
int k, s, lid;
while(scanf("%d%d",&n,&m), n || m){
init();
for(int i = 1; i <= m;i++){
scanf("%d",&k);
scanf("%d",&lid);
for(int i = 1; i < k;i++){//少一个输入
scanf("%d", &s);
un(s, lid); //每一次输入都与第一个合并;最后直接找0的集合元素个数。
}
}
int t = find(0);
printf("%d\n", -p[t]);
}
return 0;
}
/*
100 4
2 1 2
5 10 13 11 12 14
2 0 1
2 99 2
*/