题目链接:http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=DSL_1_A
有两种操作,unite和same,实现并查集的合并和查询操作。
思路:用树形结构来描述并查集,用pre数组来表示当前节点的前缀节点(父节点),初始化时所有节点的pre节点都是自己本身,所以检查是否在同一并查集内的操作就是查询这两个节点的根节点是否相同;
合并的操作就是把两个集合合并,只需要把所有的节点和指向相同的根节点即可;(书中的方法是用rank数组来存储当前树的高度,合并的时候把高度小的树合并给高度高的树)
其中涉及到了一个路径压缩的过程,因为我们在查询一个节点的根节点的时候,需要不停向它的父亲节点回溯,直到回溯到根节点为止,但是如果这个树很高,会使得回溯的过程很慢,所以我们希望,所有非根节点全都是直接连到根节点上,实现两层的树。我们可以使用递归,把节点的所有父亲节点全部指向根节点,就实现了这一条支路上的路径压缩。实现方法,也可以用循环去实现,避免数据量太大时爆栈。
具体代码如下:
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxx=10010;
int pre[maxx];
int find(int x){
if(pre[x]!=x) pre[x]=find(pre[x]);
return pre[x];
}
int main (){
for(int i=0;i<maxx;i++) pre[i]=i;
int n,m;
cin>>n>>m;
int com,x,y;
while(m--){
cin>>com>>x>>y;
if(com==0) pre[find(x)]=find(y);
else if(find(x)==find(y)) puts("1");
else puts("0");
}
return 0;
}
find函数的循环实现方法如下:
int find(int x){
int temp,p=x;
while(x!=pre[x]) x=pre[x];
while(x!=p){
temp=pre[p];
pre[p]=x;
p=temp;
}
return x;
}
错点:
1.合并操作的时候,可以不判断x和y是否在同一集合内,因为不影响结果。
2.递归的find过程中 if(pre[x]!=x) pre[x]=find(pre[x]);用pre[x]而不是x,为了使在调用find的过程中就完成了路径压缩
用rank方法实现的代码如下:
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
class DisjointSet {
public :
// rank记录树的高度
vector<int> rank, p;
DisjointSet() {};
DisjointSet(int size) {
rank.resize(size, 0);
p.resize(size, 0);
for (int i = 0; i < size; i++)
makeSet(i);
}
void makeSet(int i) {
p[i] = i;
rank[i] = 0;
}
bool same(int x, int y) {
return findSet(x) == findSet(y);
}
void unite(int x, int y) {
link(findSet(x), findSet(y));
}
void link(int x, int y) {
// 包含x的树的高度更高,则将y的树合并到x上
if (rank[x] > rank[y])
p[y] = x;
else {
p[x] = y;
if (rank[x] == rank[y])
rank[y]++;
}
}
int findSet(int x) {
if (x != p[x])
p[x] = findSet(p[x]);
return p[x];
}
};
int main()
{
int n, a, b,t, q;
scanf("%d %d", &n, &q);
DisjointSet ds = DisjointSet(n);
for (int i = 0; i < q; i++) {
scanf("%d %d %d", &t, &a, &b);
if (t == 0)
ds.unite(a, b);
else
printf("%d\n", ds.same(a, b) ? 1 : 0);
}
return 0;
}