zoukankan      html  css  js  c++  java
  • 并查集小记


    并查集:

    并查集,一种树型的数据结构,处理一些不相交集合的合并及查询问题。比方问题:某个家族人员过于庞大,要推断两个人是否是亲戚,不太easy。现给出某个亲戚关系图,求随意给出的两个人是否具有亲戚关系。 规定:x和y是亲戚,y和z是亲戚,那么x和z也是亲戚。假设x,y是亲戚,那么x的亲戚都是y的亲戚,y的亲戚也都是x的亲戚。


    从基本实现集合的结构出发:


    1.单纯的高速查找:

    若id同样则在一个集合中,下图中,( 2, 3, 4, 9 )为一集合, 3 和 6 不在一个集合中


    合并集合时,需逐个比較将两个要合并的集合里的元素 id 统一,慢

    缺陷:

    合并慢

    ==================================================


    2.单纯的高速合并:


    上图,3 的祖先是 9, 5 的祖先是 6,则 3 和 5 不在一个集合中。

    若要合并 3 和 5 所在集合,则随意选择一个集合的祖先接到还有一个集合的祖先上去,其它节点信息不动。

    例如以下合并 3, 5:



    高速合并的过程图:


    缺陷:

    树增高,退化成链表。

    ===================================================


    3.按重合并:

    将节点数小的集合拼接到节点数大的集合中去,仅仅改动小集合的根节点的父亲,其它节点父亲不动。按重合并能保证树的平缓增长。




    ==================================================


    4.路径压缩:

    如若找 9 的祖先,则从 9 到 0 的路径上的全部节点拼接到0上面。




    =================================================


    5.按重合并 加上 路径压缩(并查集):

    过程图例如以下



    C++ 代码:

    #include <iostream>
    #include <string.h>
    using namespace std;
    #define NODE_SIZE 100
    
    int set_weights[NODE_SIZE];
    int parent[NODE_SIZE];
    
    void init(){
        for( int i = 0; i < NODE_SIZE; ++i ){
            set_weights[i] = 1;
            parent[i] = i;
        }
    }
    
    // 寻找改点的祖先 当该点的值 等于 本身的父亲 才是集合的根,返回
    int find_ancestor( int i ){
        if( i != parent[i] )
            parent[i] = find_ancestor( parent[i] );
        return parent[i];
    }
    
    void merge_set( int a, int b ){
        int set_a = find_ancestor( a );
        int set_b = find_ancestor( b );
    
        if( set_a == set_b )
            return;
        // 将重量小的集合拼接到重量大的集合上
        if( set_weights[set_a] >= set_weights[set_b] ){
            set_weights[set_a] += set_weights[set_b];
            // 更新重量小的集合的根的父亲
            parent[set_b] = set_a;
        }
        else{
            set_weights[set_b] += set_weights[set_a];
            parent[set_a] = set_b;
        }
    }
    
    int main(){
        init();
        merge_set( 3, 4 );
        for( int i = 0; i <= 9; ++i )
            cout << parent[i] << " ";
        cout << endl;
    
        merge_set( 4, 9 );
        for( int i = 0; i <= 9; ++i )
            cout << parent[i] << " ";
        cout << endl;
    
        merge_set( 8, 0 );
        for( int i = 0; i <= 9; ++i )
            cout << parent[i] << " ";
        cout << endl;
    
        merge_set( 2, 3 );
        for( int i = 0; i <= 9; ++i )
            cout << parent[i] << " ";
        cout << endl;
    
        merge_set( 5, 6 );
        for( int i = 0; i <= 9; ++i )
            cout << parent[i] << " ";
        cout << endl;
    
        merge_set( 5, 9 );
        for( int i = 0; i <= 9; ++i )
            cout << parent[i] << " ";
        cout << endl;
    
        merge_set( 7, 3 );
        for( int i = 0; i <= 9; ++i )
            cout << parent[i] << " ";
        cout << endl;
    
        merge_set( 4, 8 );
        for( int i = 0; i <= 9; ++i )
            cout << parent[i] << " ";
        cout << endl;
    
        merge_set( 6, 1 );
        for( int i = 0; i <= 9; ++i )
            cout << parent[i] << " ";
    }
    

    执行结果:



    应用:

    一个非常经典的物理模型, N * N 的格子,每一个格子有两种状态vacant“空白”或者occupied“被占用”,网格能被过滤,当且仅当顶部和底部能被“vacant”状态的格子链接。例如以下,蓝色的是“空白”,白色的是“占用”,则第一幅图是能过滤的,第二幅图是不能被过滤的。



    如今知道vacant状态的格子在图中出现的概率 P* 可以影响到图是否能被过滤,如今要求 P*。

    P < P* ,差点儿都不能被过滤,

    P > P* ,差点儿都能被过滤。

    (这个问题乍一看和Union-Find没什么关系)

    但可这么做:

    1.将格子所有初始化为被占用

    2.实现 make_site_vacant() 操作,用于将邻接的格子用 Union 操作联系在一起

    3.让全部处于顶部和底部的格子变为vacant

    4.再随机将其它的格子变为vacant,直到可以find( 顶部,底部 )

    5.估算出P*


  • 相关阅读:
    手动在本机仓库中放置jar包以解决Maven中央仓库不存在该资源的问题
    同一套代码部署到两台机器上,只有一台机器上的页面中文乱码
    Nginx与httpd共存
    [Z3001] connection to database 'zabbix' failed: [2003] Can't connect to MySQL server on 'x.x.x.x' (13)
    Excel中时间戳转换公式及原理
    springcloud服务注册和发现
    spingboot和springcloud简记
    postgres use
    访问者模式
    uml类图详解
  • 原文地址:https://www.cnblogs.com/lcchuguo/p/4501136.html
Copyright © 2011-2022 走看看