集训第一天
并查集(Union-find Sets),数据结构,它主要用于处理一些不相交集合的合并问题。常见的求连通子图、求最小生成树的 Kruskal 算法和求最近公共祖先(Least Common Ancestors, LCA)等
并查集的基本操作有三个:
- makeSet(s):建立一个新的并查集,其中包含 s 个单元素集合。
- unionSet(x, y):把元素 x 和元素 y 所在的集合合并,要求 x 和 y 所在的集合不相交,如果相交则不合并。
- find(x):找到元素 x 所在的集合的代表,该操作也可以用于判断两个元素是否位于同一个集合,只要将它们各自的代表(根节点)比较一下就可以了。
makeSet时间复杂度是O(n)~~~
find 操作,如果每次都沿着父节点向上查找,那时间复杂度就是树的高度,完全不可能达到常数级。这里需要应用一种非常简单而有效的策略——路径压缩。
路径压缩,就是在每次查找时,令查找路径上的每个节点都直接指向根节点,并且趋向扁平。
最后是合并操作 unionSet,并查集的合并也非常简单,就是将一个集合的树根指向另一个集合的树根。
模板代码
//并查集
const int maxn=03xfffffff;
int p[maxn],num[maxn] ;
int count;// number of components
int find(int x)// 将p节点的父节点设置为它的爷爷节点
{
if(x!=p[x])p[x]=find(p[x]);
return p[x];
}
int make(int n){
for(i=0;i<n;i++)
{
p[i]=i;// 每个节点的组号就是该节点的序号
num[i]=1;// 初始情况下,每个组的大小都是1
count=n;
}
}
void merge(int x,int y)
{
x=find(x);
y=find(y);// 获得x和y的组号
if(x==y)return;// 如果两个组号相等,直接返回
if(num[x]<num[y])
p[x]=y;num[y]+=num[x];//x所在的树会被作为y所在树的子树,从而实现两颗独立的树的融合。
else{p[y]=x;num[x]+=num[y];}
count--;
}
POJ 1611
题意:学校里面有n个同学,编号为(0 ~ n-1),其中0号同学为SARS的疑似患者。给出m组同学的集合,每个集合里的同学都经常在一起,问这n名同学中有几个是疑似患者。(与0号同学有直接或间接接触的都算为疑似患者,包括0号同学)
思路:基础的并查集。//按秩合并需要空间较大,但是时间少
源代码1:(396K 16MS) //保留。。。。
#include<iostream>
using namespace std;
const int Max = 30050;
int parent[Max], rank[Max];
int n, m;
void make_set(){
for(int x = 0; x < n; x ++){
parent[x] = x;
rank[x] = 0;
}
}
int find_set(int x){
if(x != parent[x])
parent[x] = find_set(parent[x]);
return parent[x];
}
void union_set(int x, int y){
x = find_set(x);
y = find_set(y);
if(x == y) return;
if(rank[x] > rank[y])
parent[y] = x;
else{
parent[x] = y;
if(rank[x] == rank[y])
rank[y] ++;
}
}
int main(){
int sum, x, y;
while(scanf("%d %d", &n, &m) != EOF){
if(n == 0 && m == 0) break;
make_set();
while(m --){
scanf("%d", &sum);
scanf("%d", &x);
sum --;
while(sum --){
scanf("%d", &y);
union_set(x, y);
}
}
sum = 1; // 计算与0号同学在同一集合的同学数。(具有相同的集合代表)
for(x = 1; x < n; x ++)
if(find_set(x) == find_set(0))
sum ++;
printf("%d\n", sum);
}
return 0;
}
源代码2:(396K, 32MS) // 省去了按秩合并。每个根增加了子节点的数量。
#include<iostream>
using namespace std;
const int Max = 30050;
int parent[Max], num[Max];
int n, m;
void make_set(){
for(int x = 0; x < n; x ++){
parent[x] = x;
num[x] = 1;
}
}
int find_set(int x){
if(x != parent[x])
parent[x] = find_set(parent[x]);
return parent[x];
}
void union_set(int x, int y){
x = find_set(x);
y = find_set(y);
if(x == y) return;
parent[y] = x;
num[x] += num[y];
}
int main(){
int sum, x, y;
while(scanf("%d %d", &n, &m) != EOF){
if(n == 0 && m == 0) break;
make_set();
while(m --){
scanf("%d", &sum);
scanf("%d", &x);
sum --;
while(sum --){
scanf("%d", &y);
union_set(x, y);
}
}
printf("%d\n", num[find_set(0)]);
}
return 0;
}