• 【数据结构】【王道】【树与二叉树】并查集的实现及优化(可直接运行)


    总目录:https://blog.csdn.net/treesorshining/article/details/125726400

    1.概述

    并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题,常常在使用中以森林来表示。正如其名,主要有查找与合并两个操作。

    2.基本操作

    2.1 初始化

    初始化将所有结点的双亲设为-1。

    // 并查集初始化操作
    void Initial(int S[]) {
        // 初始化将所有结点的双亲设为-1
        for(int i = 0;i < SIZE;i++) {
            S[i] = -1;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2.2 查(时间复杂度O(n))

    查操作,找到x所属结合(即返回x所属根结点),时间复杂度O(n)。

    // 查操作,找到x所属结合(即返回x所属根结点)
    // 时间复杂度O(n)
    int Find(int S[], int x) {
        // 循环寻找x的根,根的双亲为-1,循环结束则表示已找到双亲
        while(S[x] >= 0) {
            x = S[x];
        }
        return x;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.3 并(时间复杂度O(1))

    并操作,将两个集合合并为一个,时间复杂度O(1)。

    void Union(int S[], int Root1, int Root2) {
        // Root1与Root2是相同集合则不可合并
        if(Root1 == Root2) {
            return;
        }
        // 合并即是将令一棵树称为另一棵树的子树
        // 将根Root2连接到另一根Root1下面
        S[Root2] = Root1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.4 并优化(查时间复杂度O(log2n))

    Union操作优化
    目的:减少Find操作的所需的时间
    方法:在构建新树时,尽可能不让树长高(这样可以降低Find操作最坏时间复杂度)
    具体实现方法:将根结点的值改为(-结点数,即负结点数),再根据此为依据,让小树合并到大树上
    通过此优化树的高度不会超过log2n(向下取整) + 1
    之后Find操作的最坏时间复杂度为O(log2n)

    // Union操作优化
    // 目的:减少Find操作的所需的时间
    // 方法:在构建新树时,尽可能不让树长高(这样可以降低Find操作最坏时间复杂度)
    // 具体实现方法:将根结点的值改为(-结点数),再根据此为依据,让小树合并到大树上
    // 通过此优化树的高度不会超过log2n(向下取整) + 1
    // 之后Find操作的最坏时间复杂度为O(log2n)
    void UnionOptimize(int S[], int Root1, int Root2) {
        // Root1与Root2是相同集合则不可合并
        if(Root1 == Root2) {
            return;
        }
        // 比较树的结点数,将小树合并到大树
        if(S[Root2] > S[Root1]) {
            // 更新树的结点数
            S[Root1] += S[Root2];
            // 合并
            S[Root2] = Root1;
        } else {
            // 更新树的结点数
            S[Root2] += S[Root1];
            // 合并
            S[Root1] = Root2;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    2.5 查优化(时间复杂度O(a(n))

    Find操作优化(压缩路径)
    先找到根结点,再将查找路径上所有结点都挂到根结点下
    在再次寻找时,只需要很短路径就能找到了
    每次Find操作,先找根,再压缩路径,可使树的高度不超过O(a(n))
    a(n)是一个增长缓慢的函数,对于常见n值,通常a(n)<=4

    // Find操作优化(压缩路径)
    // 先找到根结点,再将查找路径上所有结点都挂到根结点下
    // 在再次寻找时,只需要很短路径就能找到了
    // 每次Find操作,先找根,再压缩路径,可使树的高度不超过O(a(n))
    // a(n)是一个增长缓慢的函数,对于常见n值,通常a(n)<=4
    int FindOptimize(int S[], int x) {
        int root = x;
        while(S[root] >= 0) {
            // 循环找到根结点
            root = S[root];
        }
        // 压缩路径
        while(x != root) {
            // 令t指向x的父结点
            int t = S[x];
            // x直接挂到根结点下
            S[x] = root;
            x = t;
        }
        // 返回结点编号
        return root;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    3.完整代码

    #include
    
    #define SIZE 13
    
    // 通常用数组元素下标代表元素名,用根节点下标代表子集合名,根节点双亲默认设为-1
    // 集合元素数组(双亲指针数组)
    int UFSets[SIZE];
    
    // 并查集初始化操作
    void Initial(int S[]) {
        // 初始化将所有结点的双亲设为-1
        for(int i = 0;i < SIZE;i++) {
            S[i] = -1;
        }
    }
    
    // 查操作,找到x所属结合(即返回x所属根结点)
    // 时间复杂度O(n)
    int Find(int S[], int x) {
        // 循环寻找x的根,根的双亲为-1,循环结束则表示已找到双亲
        while(S[x] >= 0) {
            x = S[x];
        }
        return x;
    }
    
    // 并操作,将两个集合合并为一个
    // 时间复杂度O(1)
    void Union(int S[], int Root1, int Root2) {
        // Root1与Root2是相同集合则不可合并
        if(Root1 == Root2) {
            return;
        }
        // 合并即是将令一棵树称为另一棵树的子树
        // 将根Root2连接到另一根Root1下面
        S[Root2] = Root1;
    }
    
    // Union操作优化
    // 目的:减少Find操作的所需的时间
    // 方法:在构建新树时,尽可能不让树长高(这样可以降低Find操作最坏时间复杂度)
    // 具体实现方法:将根结点的值改为(-结点数),再根据此为依据,让小树合并到大树上
    // 通过此优化树的高度不会超过log2n(向下取整) + 1
    // 之后Find操作的最坏时间复杂度为O(log2n)
    void UnionOptimize(int S[], int Root1, int Root2) {
        // Root1与Root2是相同集合则不可合并
        if(Root1 == Root2) {
            return;
        }
        // 比较树的结点数,将小树合并到大树
        if(S[Root2] > S[Root1]) {
            // 更新树的结点数
            S[Root1] += S[Root2];
            // 合并
            S[Root2] = Root1;
        } else {
            // 更新树的结点数
            S[Root2] += S[Root1];
            // 合并
            S[Root1] = Root2;
        }
    }
    
    // Find操作优化(压缩路径)
    // 先找到根结点,再将查找路径上所有结点都挂到根结点下
    // 在再次寻找时,只需要很短路径就能找到了
    // 每次Find操作,先找根,再压缩路径,可使树的高度不超过O(a(n))
    // a(n)是一个增长缓慢的函数,对于常见n值,通常a(n)<=4
    int FindOptimize(int S[], int x) {
        int root = x;
        while(S[root] >= 0) {
            // 循环找到根结点
            root = S[root];
        }
        // 压缩路径
        while(x != root) {
            // 令t指向x的父结点
            int t = S[x];
            // x直接挂到根结点下
            S[x] = root;
            x = t;
        }
        // 返回结点编号
        return root;
    }
    
    int main() {
        int temp[13] = {-1, 0, -1, -1, 1, 1, 2, 3, 3, 3, 4, 4, 7};
        for(int i = 0;i < SIZE;i++) {
            UFSets[i] = temp[i];
        }
        printf("%d\n", Find(UFSets, 0));
        printf("%d\n", Find(UFSets, 3));
        Union(UFSets, 0, 3);
        printf("%d\n", Find(UFSets, 0));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96

    4.运行结果

    在这里插入图片描述

  • 相关阅读:
    服务器时间正常,docker容器日志显示时间少了8小时
    Dubbo 动态调用 group
    React 防抖与节流用法
    利用谷歌云跑代码
    软件工程与计算总结(二十)软件交付
    树莓派开发笔记(十二):入手研华ADVANTECH工控树莓派UNO-220套件(一):介绍和运行系统
    day44 数据库查询命令
    80-Java的Map集合:概述、API、遍历方式
    【2013年数据结构真题】
    移动端适配和响应式页面中的常用单位
  • 原文地址:https://blog.csdn.net/treesorshining/article/details/126028472