• 并查集 (Disjoint Set)


    本文链接:http://i.cnblogs.com/EditPosts.aspx?postid=5408816

    问题:

      在某个城市里,住着N个人,这N个人都有自己的公司,现给出N个人的M条信息(即某两个人属于同一个公司),问这个城市最多有多少个公司。

    THINK:

    比如给出

    10 6

    1 3

    3 7

    2 5

    5 9

    9 10

    6 8

      代表有10个人,6个关系,1 和 3 在一个公司里, 3 和 7 在一个公司里,2 和 5 在一个公司里, 5 和 9 在一个公司里,9 和 10 在一个公司里, 6 和 8 在一个公司里。那么1 3 7则在一个公司里,2 5 9 10在另外一个公司里,4 单独在一个公司里,6 和8 在一个公司里,则这个城市最多有 4 个公司。

      把这个问题数学化,即有N个元素,这N个元素都属于某一个集合,给出M个关系,代表某两个元素在一个集合里,问最多有多少个集合,今天讨论的并查集算法可以很好的解决这个问题。

    并查集(Disjoint Set):

      把这N个元素初始化为N个不相交集合,在每个集合里面选择其中某个元素代表所在集合的名字。

      常见操作:

      1):合两个集合;

      2):找某元素所在集合

      即“并查集”。

      具体实现:

      用编号最小的元素的标记某个集合。

      定义一个数组pre[1...N],其中pre[i]代表i所在集合。

      

      则最后的集合为:{1, 3, 7},{2, 5, 9, 10},{4}, {6, 8},第一个集合代号为 1 ,第二个集合代号为 2 ,第三个集合代号为 4 ,第四个集合代号为 6。

    初始化代码:

    void initPre()
    {
        for(int i = 1; i <= N; ++i)
            pre[i] = i;
    }

    查找代码:

    int Find(int x)    
    {
       int r = x;
       while (pre[r] != r)
          r = pre[r];
       return r;
    }

    合并代码:

    void mix(int x, int y)
    {
        int fx = Find(x);
        int fy = Find(y);
        if(fx < fy) 
            pre[fy] = fx;
        if(fx > fy)
            pre[fx] = fy;
    }

      初始化代码比较容易理解,下面来解释查找代码。拿上面第二个集合来说吧,比如查找 10 在哪个集合里面,前面说过,pre[i] 代表 i 所在集合的编号,那么看pre[10],因为pre[10] 等于 9 那么 10 在 代号为 “9” 的集合里面吗?这个显然不是,因为上面根本不存在代号为 “9” 的集合,这是为什么?之前说过用这个集合里面元素最小的数字代表这个集合的编号,则代表这个集合编号的元素的 pre[i] 一定等于 i,由于pre[9] != 9,所以继续往上找,找到了5,pre[5] != 5,那么继续向上找到 1 ,pre[1] = 1,即找到了代表 10 所在集合的编号为 “1”,这就是查找操作。

      再来解释合并操作,比如我们要合并 8 和 10,我们先找到 8 所在集合的编号为 “6”,10 所造集合的编号为 “1”,由于 10 所在集合的编号小于 8 所在集合的编号,于是就可以让 8 所在集合的编号 “6” 改成 10 所在集合的编号 “1”,即 pre[6] = 1,于是就完成了合并操作。

      到了这里你有没有觉得有什么不妥的地方呢?比如再次查找 10 所在的集合,是不是又得一步一步的向上查找?的确是的,可是刚才已经查找到了 10 所在的集合编号为 “1”,为什么还有查找呢?由于并没有改变 pre[10] 的值,那么每次查找都需要做这样的动作。所以当查找到10所在的集合编号为 “1” 时可以直接让pre[10] = 1。那么就这么结束了吗,还没有,由于在查找 10 的过程中还查找了 9 ,那么顺便还可以让pre[9] = 1,于是下次查询时就很省时间了,这就是所谓的路径压缩。

      (推荐一个详解路径压缩的博客:http://blog.csdn.net/niushuai666/article/details/6662911)

    含路径压缩的查找代码:

    int Find(int x)  
    {  
        int r = x;  
        while(r != pre[r])  
            r = pre[r];  
        int i = x, j;  
        while(pre[i]!=r)  
        {  
            j = pre[i];  
            pre[i] = r;  
            i = j;  
        }  
        return r;  
    } 

    递归压行式:

    int Find(int x){ return x == pre[x] ? x : pre[x] = Find(pre[x]); }
  • 相关阅读:
    Codeforces Round #551 (Div. 2) F. Serval and Bonus Problem (DP/FFT)
    Codeforces Round #551 (Div. 2) E. Serval and Snake (交互题)
    BZOJ 5495: [2019省队联测]异或粽子 (trie树)
    洛谷【P2669】NOIP2015普及组 T1金币
    解决Win 10上SSD缓慢问题
    如何保障数据安全
    一个网工的linux学习过程
    JS实现select去除option的使用注意事项
    codevs1506传话(kosaraju算法)
    我的园子
  • 原文地址:https://www.cnblogs.com/Ash-ly/p/5408816.html
Copyright © 2020-2023  润新知