数据结构——并查集

一、并查集的定义

  并查集是一种维护集合的数据结构,它的名字中“并”“查”“集”分别取自 Union(合并)、Find(查找)、Set(集合)这 3 个单词。也就是说,并查集支持下面两个操纵:

    1.  合并:合并两个集合。
    2.  查找:判断两个元素是否在一个集合。      

  并查集的实现就是用一个数组:

int father[N];            // 表示元素的父亲结点 

 

 

  例如 father[1]=2 就表示元素 1 的父亲结点是元素 2。另外,如果 father[i]==i,则说明元素 i 是该集合的根结点,但对同一集合来说只存在一个根结点,且将其作为所属集合的标识

 

 

二、并查集的基本操作

  总体来说,并查集的使用需要先初始化 father 数组,然后再根据需要进行查找或合并的操作。

  1. 初始化

  一开始,每个元素都是独立的一个集合,因此需要令所有 father[i] 等于 i。代码如下:

1 // 初始化 
2 void init(int n) {
3     int i;
4     for(i=1; i<=n; ++i) {
5         father[i] = i;    // 每个元素都是独立的集合 
6     }
7 }

 

 

  2. 查找

  由于规定同一集合中只存在一个根结点,因此查找操作就是对给定的结点寻找其根结点的过程。实现思路就是,反复寻找父亲结点,直到找到根结点(即 father[i]==i 的结点)。代码如下:

1 // 对给定的结点寻找其根结点
2 int findFather(int x) {
3     if(x == father[x])    return x;        // 如果找到根结点,则返回根结点编号 x 
4     else return findFather(father[x]);    // 否则,递归判断 x 的父亲结点是否是根结点 
5 } 

 

 

  3. 合并

  合并是指把两个集合合并成一个集合,题目中一般给出两个元素,要求把这两个元素所在的集合合并。实现步骤如下:

    1.  对于给定的两个元素 a、b,判断它们是否属于同一集合。可以调用上面的查找函数,对这两个元素 a、b 分别查找根结点,然后再判断其根结点是否相同。
    2.  合并两个集合:在 1 中已经获得两个元素的根结点 faA 与 faB,因此只需要把其中一个的父结点指向另一个结点。

  代码如下:

1 // 把 a,b 所在的集合合并
2 void Union(int a, int b) {
3     int faA = findFather(a);    // 查找 a 的根结点 
4     int faB = findFather(b);    // 查找 b 的根结点
5     if(faA != faB) {            // 若 a,b 属于不同集合 
6         father[faA] = faB;        // 合并它们 
7     }
8 } 

 

    

 

三、路径压缩

  可以在上述 findFather 函数里把当前查询结点的路径上的所有结点的父亲都指向根结点,查找的时候就不需要一直回溯去找父亲了,查询的复杂度可以降为 O(1)。具体步骤如下:

    1.  按原先的写法获得 x 的根结点 r。
    2.  重新从 x 开始走一遍寻找根结点的过程,把路径上经过的所有结点的父亲改为根结点 r。

  代码如下:

 1 int findFather(int x) {
 2     int a = x;                    // 保存原结点
 3     while(x != father[x]) {        // 寻找根结点 
 4         x = father[x];
 5     } 
 6     // 重新走一遍寻找根结点的过程 
 7     while(a != father[a]) {
 8         int z = a;                // 保存结点 a 
 9         a = father[a];            // 回溯父亲结点 
10         father[z] = x;            // 将所有结点的父亲改为根结点 x 
11     } 
12     
13     return x;                    // 返回根结点 
14 }

 

 

 

  下面是一个简单使用并查集的例子。

  题目截图:

  

 

  思路:

  需要使用上诉讲的并查集思想,先初始化并查集,然后对输入的每一对好朋友进行合并操作。同时,应设置 isRoot[N] 来记录每个结点是否作为某个集合的根结点,这样当处理完输入数据后就可以累加 isRoot 数组得到集合数目。

 

  代码如下: 

 1 /*
 2     并查集 
 3 */
 4 
 5 #include <stdio.h>
 6 #include <string.h>
 7 #include <math.h>
 8 #include <stdlib.h>
 9 #include <time.h>
10 #include <stdbool.h>
11 
12 #define N 102 
13 int father[N];            // 表示元素的父亲结点 
14 int isRoot[N] = {0};    // 若为1, 表示为根结点 
15 
16 // 初始化 
17 void init(int n) {
18     int i;
19     for(i=1; i<=n; ++i) {
20         father[i] = i;    // 每个元素都是独立的集合 
21         isRoot[i] = 1;    // 标记结点是根结点 
22     }
23 }
24 
25 /* 
26 // 对给定的结点寻找其根结点
27 int findFather(int x) {
28     if(x == father[x])    return x;        // 如果找到根结点,则返回根结点编号 x 
29     else return findFather(father[x]);    // 否则,递归判断 x 的父亲结点是否是根结点 
30 } 
31 */
32 
33 int findFather(int x) {
34     int a = x;                    // 保存原结点
35     while(x != father[x]) {        // 寻找根结点 
36         x = father[x];
37     } 
38     // 重新走一遍寻找根结点的过程 
39     while(a != father[a]) {
40         int z = a;                // 保存结点 a 
41         a = father[a];            // 回溯父亲结点 
42         father[z] = x;            // 将所有结点的父亲改为根结点 x 
43     } 
44     
45     return x;                    // 返回根结点 
46 }
47 
48 // 把 a,b 所在的集合合并
49 void Union(int a, int b) {
50     int faA = findFather(a);    // 查找 a 的根结点 
51     int faB = findFather(b);    // 查找 b 的根结点
52     if(faA != faB) {            // 若 a,b 属于不同集合 
53         father[faA] = faB;        // 合并它们 
54         isRoot[faA] = 0;        // 更新标志 
55         isRoot[faB] = 1; 
56     }
57 } 
58 
59 int main() {
60     int n, m, i, a, b; 
61     scanf("%d %d", &n, &m);        // 输入数码宝贝的个数和好朋友组数 
62     init(n);                    // 初始化并查集 
63     for(i=0; i<m; ++i) {
64         scanf("%d %d", &a, &b);    // 输入每一对朋友 
65         Union(a, b);            // 合并 
66     }
67     int ans = 0;
68     for(i=1; i<=n; ++i) {        // 累加得到集合数目 
69         ans += isRoot[i];
70     } 
71     printf("%d\n", ans);        // 按要求输出 
72 
73     return 0;
74 }

 

   

posted @ 2018-02-07 11:08  Just_for_Myself  阅读(497)  评论(0编辑  收藏  举报