ACM-数据结构-并查集

ACM竞赛中,并查集(DisjointSets)这个数据结构经常使用。顾名思义,并查集即表示集合,并且支持快速查找、合并操作。

并查集如何表示一个集合?它借助树的思想,将一个集合看成一棵有根树。那又如何表示一棵树?初始状态下,一个元素即一棵树,根即是元素本身。


并查集如何支持合并操作?不难发现,按照树的思想,在同一棵树中的所有元素,根都是相同的。也就是说,合并两个不同的集合,只需要将其中一个集合的根设置为另一个集合的根即可,而需要改变根的那个集合,其实只需要改变根节点的父节点即可。


并查集如何支持快速查找操作?如果完全按照上面的合并方法进行合并操作,最后生成的树,可能是完全线性的,那么查询的时间复杂度就退化成了O(n),因为在这种情况下,程序不得不遍历完所有节点才能查询到当前元素所属的根节点。


路径压缩算法优化并查集查询操作。按照集合原来的定义,集合中的元素是满足无序性的,因此可以在查询操作进行的过程中,当程序遍历到根节点然后返回的时候,将所有属于当前根节点的元素的父节点直接设置为当前根节点。如此一来,原来的一条链就变成了一般的树了。当下一次查询的时候,就可以很快的遍历到根节点了,复杂度下降为O(1)。


还有一种优化查询速度的方法,那就是合并两个集合的时候,按秩进行合并,这里的秩代表的以当前元素为根节点的元素个数。很明显,将秩较小的树合并到秩较大的树上更优。

最后,就是具体如何用代码实现并查集?其实,并查集中只涉及到了保存当前元素的父节点这一信息,所以利用一个数组set[i]代表节点i的父节点即可,如果set[i]=i那么代表当前集合的根即为i元素本身。


以一道例题为例,HDOJ:1212,时空转移(点击打开链接):

How Many Tables

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 17478    Accepted Submission(s): 8574


Problem Description
Today is Ignatius' birthday. He invites a lot of friends. Now it's dinner time. Ignatius wants to know how many tables he needs at least. You have to notice that not all the friends know each other, and all the friends do not want to stay with strangers.

One important rule for this problem is that if I tell you A knows B, and B knows C, that means A, B, C know each other, so they can stay in one table.

For example: If I tell you A knows B, B knows C, and D knows E, so A, B, C can stay in one table, and D, E have to stay in the other one. So Ignatius needs 2 tables at least.
 

Input
The input starts with an integer T(1<=T<=25) which indicate the number of test cases. Then T test cases follow. Each test case starts with two integers N and M(1<=N,M<=1000). N indicates the number of friends, the friends are marked from 1 to N. Then M lines follow. Each line consists of two integers A and B(A!=B), that means friend A and friend B know each other. There will be a blank line between two cases.
 

Output
For each test case, just output how many tables Ignatius needs at least. Do NOT print any blanks.
 

Sample Input
  
  
2 5 3 1 2 2 3 4 5 5 1 2 5
 

Sample Output
  
  
2 4
 

Author
Ignatius.L
 

Source
 

Recommend
Eddy   |   We have carefully selected several similar problems for you:   1325  1198  1863  1102  1162 
 

题意:

给出一些人之间关系,如果两个人有直接或间接关系,那么这两个人就属于同一个集合,最后统计集合的个数。

分析:

并查集思想,初始每一个人属于各自一个集合,如果当前读入的两个有关系的人不在同一集合,那么就合并他们所属的集合,集合个数减一。

源代码:

#include <cstdio>

const int NumSets = 1005;
typedef int DisjSet[NumSets + 1];
typedef int Rank[NumSets + 1];
DisjSet S;
Rank R;

// Initialize the set and rank
void Initialize()
{
    for(int i=0; i<NumSets; ++i)
    {
        S[i] = i;
        R[i] = 1;
    }
}

// Find father of the value, with the function of path compression
int Find(int value)
{
    if(S[value] != value) S[value] = Find(S[value]);
    return S[value];
}

// Union the value1 and value2 by the rank of the set which them local in
void SetUnion(int value1, int value2)
{
    int fa1 = Find(value1);
    int fa2 = Find(value2);
    if(fa1 == fa2) return ;
    if(R[fa1] >= R[fa2])
    {
        S[fa2] = fa1;
        R[fa1] += R[fa2];
    }
    else
    {
        S[fa1] = fa2;
        R[fa2] += R[fa1];
    }
}

int main()
{//freopen("sample.txt", "r", stdin);
    int cas;
    scanf("%d", &cas);
    while(cas--)
    {
        int n, m;
        Initialize();
        scanf("%d%d", &n, &m);
        for(int i=0; i<m; ++i)
        {
            int a, b;
            scanf("%d%d", &a, &b);
            if(Find(a) != Find(b))
            {
                SetUnion(a, b);
                --n;
            }
        }
        printf("%d\n", n);
    }
    return 0;
}

其它并查集题目还有,HDOJ:1232、1558、1811、1829、1198。UESTC:203、1070。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
高精运算: typedef struct //为方便处理,用结构体 { int len ; long num [1024] ; } HNum ; //万进制高精加法, 注意输出高位补0, printf ("%04d" …) ; void HPlus (HNum &a, HNum &b, HNum &c) { int i, len = a.len > b.len ? a.len : b.len ; memset (&c, 0, sizeof (HNum)) ; for (i = 1 ; i <= len ; i ++) { c.num [i] += a.num [i] + b.num [i] ; if (c.num [i] >= BASE) { c.num [i+1] += c.num [i] / BASE ; c.num [i] %= BASE ; } } c.len = len ; while (c.num [c.len+1] > 0) c.len ++ ; } //万进制高精乘法 void HMul (HNum &a, HNum &b, HNum &c) { int i, j ; memset (&c, 0, sizeof (HNum)) ; for (i = 1 ; i <= a.len ; i ++) for (j = 1 ; j <= b.len ; j ++) { c.num [i+j-1] += a.num [i] * b.num [j] ; //注意+号 if (c.num [i+j-1] >= BASE) { c.num [i+j] += c.num [i+j-1] / BASE ; //注意+号 c.num [i+j-1] %= BASE ; } } c.len = a.len + b.len - 1 ; while (c.num [c.len+1] > 0) // c.len ++ ; } //万进制高精减法 void HSub (HNum &a, HNum &b, HNum &c) { int i, len = a.len ; //保证a >= b memset (&c, 0, sizeof (HNum)) ; for (i = 1 ; i <= len ; i ++) { c.num [i] += a.num [i] - b.num [i] ; //注意+号 if (c.num [i] < 0) { c.num [i+1] -= 1 ; //注意-号 c.num [i] += BASE ; } } c.len = len ; while (c.len > 0 && c.num [c.len] == 0) c.len -- ; } //万进制高精减法, 直接就 long long…. -------------------------------------------------------------------------------- //Fibonacci, Fibo [i] = Fibo [i-1] + Fibo [i-2], Fibo [3] = 3 ; // Catalan数列S[n] = C(2n,n)/(n+1) long Catalan (long n) { long i, x, y ; x = y = 1 ; for (i = 2 ; i <= n ; i ++) x *= i ; for (i = n ; i <= 2*n ; i ++) y *= i ; return y/x/(n + 1) ; } //最小公倍数 long lcm (long a, long b) { return a*b/gdc (a, b) ; } //最大公约数, 辗转相除法 long gdc (long a, long b) { return (a%b == 0)? b : gdc (b, a%b) ; } ------------------------------------------------------------------------------------------------------------ //堆操作 void In (HeapDT dt) //进堆 { int i ; list [++ len] = dt ; i = len ; while (i > 1) //向上调整 { if (list [i].w < list [i/2].w) Swap (i, i/2) ; else break ; i /= 2 ; } } HeapDT Out () //出堆 { HeapDT ret = list [1] ; Swap (1, len) ; //NOTE: 最重要的一步, 最后(最大)一个元素与第一个元素交换 len -- ; //堆长度减1 int i, pa = 1 ; for (i = pa * 2 ; i <= len ; i *= 2) //向下调整 { if (i < len && list [i+1].w < list [i].w) i ++ ; if (list [i].w < list [pa].w) Swap (pa, i) ; else break ; pa = i ; } return ret ; } ------------------------------------------------------------------------------------------------------------ //二分查找, 注意等号 while (low < high) { mid = (low + high) >> 1 ; if (strcmp (spname, name [mid]) <= 0) high = mid ; else low = mid + 1 ; } ------------------------------------------------------------------------------------------------------------ //快排 void QSort (int low, int high) { int l, r ; Milkcow p = cow [low] ; l = low, r = high ; while (l < r) { while (l < r && cow [r].price >= p.price) r -- ; cow [l] = cow [r] ; while (l < r && cow [l].price <= p.price) l ++ ; cow [r] = cow [l] ; } cow [l] = p ; if (l-1 > low) QSort (low, l-1) ; if (l+1 < high) QSort (l+1, high) ; } -------------------------------------------------------------------------------------------- //优化并查集 int FindSet (int i) { if (Parent [i] != i) //状态压缩 Parent [i] = FindSet (Parent [i]) ; return Parent [i] ; } void UnionSet (int a, int b) { int i, j ; i = FindSet (a) ; j = FindSet (b) ; if (i != j) if (Rank [i] > Rank [j]) //启发式合并:让深度较小的树成为深度较大的树的子树 Parent [j] = i ; else { Parent [i] = j ; if (Rank [i] == Rank [j]) Rank [j] ++ ; } } ------------------------------------------------------------------------------------------- 图论: //MST double Kruscal () { int i, k = 0 ; double s = 0 ; for (i = 0 ; i <= n ; i ++) Parent [i] = i ; for (i = 0 ; i < m && k < n-1; i ++) //m为总边数 { if (FindSet (Edge [i].a) != FindSet (Edge [i].b)) { s += Edge [i].v ; if (s > S) //是否超出范围 return 0 ; UnionSet (Edge [i].a, Edge [i].b) ; k ++ ; //记录合并的边数 } } if (k != n-1) return 0 ;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值