并查集

集训第一天

并查集(Union-find Sets),数据结构,它主要用于处理一些不相交集合的合并问题。常见的求连通子图、求最小生成树的 Kruskal 算法和求最近公共祖先(Least Common Ancestors, LCA)等

并查集的基本操作有三个:

  1. makeSet(s):建立一个新的并查集,其中包含 s 个单元素集合。
  2. unionSet(x, y):把元素 x 和元素 y 所在的集合合并,要求 x 和 y 所在的集合不相交,如果相交则不合并。
  3. find(x):找到元素 x 所在的集合的代表,该操作也可以用于判断两个元素是否位于同一个集合,只要将它们各自的代表(根节点)比较一下就可以了。
用一个足够长的数组来存储树节点(静态链表),那么 makeSet 要做的就是森林,其中每个元素都是一个单元素集合,即父节点是其自身:


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;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值