并查集优化模板

一,首先是查找优化模板,如果数据不大直接手动合并,配合一下查找方式即可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
*/

猜你喜欢

转载自blog.csdn.net/qq_40596572/article/details/103946279
今日推荐