并查集

在做天梯赛例题时,暴力二维数组做7-7 排座位 (25分)一直有一个点过不了,百度发现都用的并查集做的,学习一下。
参考

并查集(Union-Find)

合并(Union):把两个不相交的集合合并为一个集合。
查询(Find):查询两个元素是否在同一个集合中。
适合解决,亲戚的亲戚等问题

使用数组模拟树状结构,改变父节点值连接两个树,f[i] 表示 i 的父节点。
由作图状态到右图状态:
f [ 8 ] = { − 1 , 3 , 3 , 3 , 3 , 6 , 6 , 7 } … 排 除 f [ 0 ] f[8] = \{-1,3,3,3,3,6,6,7\}\dots排除f[0] f[8]={1,3,3,3,3,6,6,7}f[0]
f [ 7 ] = 6 f[7] = 6 f[7]=6

## 初始化
void init(int n){
	for(int i = 1; i <= n; i++){
		f[i] = i;
	}
}

Find

因为可以看作相同亲属关系的元素在一个树上,查询两个元素在一个集合中,只需要判断这两个元素的根节点是否相同。

//查询一个元素的根节点
int find(int x){
	if(x == f[x]){
		return x;//元素的父节点是他本身,那么它就是它所在集合的根节点
	}else{
		return find(f[x]);//否则查询其父节点的父节点
	}
}

Union

把两个不相交的集合合并为一个集合。只需要把待加入集合x节点指向集合y节点,即f[find(x)] = find(y)

void merge(int x, int y){
	f[find(x)] = find(y);
}

路径压缩

以上简单的代码基本上已经实现的并查集,但是还可以进行部分优化,假设出现下面的情况,执行merge(3,4),此时,不难设想,当 3 → 2 → 1 3\to2\to1 321很长时,加入集合时间复杂度将会变得非常大,对于集合来说,只需表示元素在同一集合中,无需考虑连接情况。此时可以将一个集合内的所有元素指向根节点。这种方法就叫做路径压缩。

对find方法进行改造

int find(int x){
	if(x == f[x]){
		return x;//元素的父节点是他本身,那么它就是它所在集合的根节点
	}else{
		f[x] = find(f[x]);//父节点设为根节点
		return f[x];//父节点
	}
}
//以上代码还可以简写为

int find(int x){
	return x==f[x]?x:(f[x]=find(f[x]));
}

按秩合并

在集合合并时,经常会出现以下问题,是① merge(7,1) 还是 ② merge(1,7),显然应当采用merge(7,1)的方式。假设采用merge(1,7)的方法,新生成的树深度增加,这样在基本每次find(x)时,时间复杂度都会增加,所以,我们应当每次合并时将简单树往复杂树上合并

方法
加入rank[]数组,记录每个节点的深度,此时该节点看作根节点,初始化时,每个节点深度为1。
改造merge方法。
初始化

void init(int n){
	for(int i = 1; i <= n; i++){
		f[i] = i;
		rank[i] = 1;
	}
}
void merge(int x, int y){
	int i = find(x), j = find(y);
	if(rank[i]<=rank[j]){
		f[i] = j;
	}else{
		f[j] = i;
	}
	if(rank[i]==rank[j]&&i!=j){
		rank[j]++;//旧节点加入新树,树深度加1
	}
}

例题

下面再看一下7-7 排座位 (25分)这道题,只需要对朋友关系进行并查集,敌对关系单独用二维数组进行单独处理。对两个人的两个关系进行分类讨论,就能得出答案。
ac代码

#include<iostream>
#include<cmath>
#include<cstring>
#include<string>
#include<cstdio>
#include<algorithm>
#include<set>
using namespace std;
int rl[101][101] = {0};
int f[200] = {0}, rank[200] = {0};
int find(int x){
	return x==f[x]?x:(f[x] = find(f[x]));
}
void merge(int i, int j){
	int x = find(i), y = find(j);
	if(rank[x]<=rank[y]){
		f[x] = y;
	}else{
		f[y] = x;
	}
	if(rank[x]==rank[y]&&x!=y){
		rank[y]++;
	}
}
void init(int n){
	for(int i = 1; i <= n; i++){
		f[i] = i;
		rank[i] = 1;
	}
}
int main(){
	int n, m, k, a, b, T;
	cin >> n >> m >> k;
	init(n);
	for(int i = 0; i < m; i++){
		cin >> a >> b >> T;
		if(T==1){
			merge(a,b);
		}else{
			rl[a][b] = T;
			rl[b][a] = T;
		}
	}
	for(int i = 0; i < k; i++){
		cin >> a >> b;
		if(find(a)==find(b)&&rl[a][b]!=-1){
			cout << "No problem" << endl;
		}else if(find(a)!=find(b)&&rl[a][b]!=-1){
			cout << "OK" << endl;
		}else if(find(a)==find(b)&&rl[a][b]==-1){
			cout << "OK but..." << endl;
		}else{
			cout << "No way" << endl;
		}
	}
	
	
	return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值