在做天梯赛例题时,暴力二维数组做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 3→2→1很长时,加入集合时间复杂度将会变得非常大,对于集合来说,只需表示元素在同一集合中,无需考虑连接情况。此时可以将一个集合内的所有元素指向根节点。这种方法就叫做路径压缩。
对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;
}