并查集 (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]); }
posted @ 2016-04-19 16:45  vrsashly  阅读(353)  评论(0编辑  收藏  举报