• 数据结构——并查集


    一、并查集的定义

      并查集是一种维护集合的数据结构,它的名字中“并”“查”“集”分别取自 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
    ", ans);        // 按要求输出 
    72 
    73     return 0;
    74 }

       

  • 相关阅读:
    嵌入式开发之davinci--- spi 中的时钟极性CPOL和相位CPHA
    Setting up a Single Node Cluster Hadoop on Ubuntu/Debian
    Install Java JDK JRE on Ubuntu/Debian with Apt-Get
    使用WICleanup清理Windows Installer 冗余文件
    WinSxS文件夹瘦身
    Linux时间同步+国内常用的NTP服务器地址
    How to fix Mysql table crashes
    Monitorix:一款面向Linux的轻型系统和网络监测工具
    How To Secure Apache with Let's Encrypt on Ubuntu (Free SSL)
    Latex表格太宽处理方法 (How to shorten Latex table length)
  • 原文地址:https://www.cnblogs.com/coderJiebao/p/Algorithmofnotes24.html
Copyright © 2020-2023  润新知