并查集的理解

从数学的角度来说,假设我们有n个集合{{..},{..},.......},而每个集合内又有众多的元素{A,B,C.....},{1,2,3,4......}等等,每个集合的元素都存在这或多或少的联系,什么联系不清楚,但我们只需知道存在这样的关系即可;而突然,我们希望让集合之间也联系起来,而又不想一个一个的将它们绑到一起,那么,并查集就是解决它们之间联系的数据结构。树就是我们需要用到的一种连接结构。

并查集一般包含三个主体:

1. makeSet();建立一个数组并储存每一个数的节点(可以看成他们所处在的队伍的编号);

   for(int i=1;i<=n;i++)
        per[i] = i;

2.Find(int  x);查找x的根节点,也就是x所队伍的代表人(代表人特征 :Find(n)=n);

    int find(int p)  
    {  
        while(p != id[p])  
            {  
                  p = id[p];  
            }  
        return p;  
    }        
  •  在这里我们简单的思考一下Find的效率问题,我们通过不断个根节点添加子节点的方式(也就是树),将两者联系在一起,如果树的路径变得很长,就需要从下到上一直访问下去,效率就大大降低,因此我们需要想个办法决绝路径过长的问题;
  • 路径压缩:所有的子节点,还是子子节点,全部直接连接到根节点上去;
        • int Find(int x)
          {
              if(per[x]!=x)
                  per[x] = Find(per[x]);//把属于一个队伍的人全部和老大联系起来;
              return per[x];
          }

 3.unionSet(int x,int y) :将两个关联的点连接起来;

              void Union(int a,int b)
              {
                  //if(Find(a)!=Find(b))
                     //per[Find(a)]=b;
            int ax = Find(a);
            int bx = Find(b);
            if(ax!=bx)
              per[ax]=b;
        }

  在这里,我们考虑下per[ax] = b,为甚么一定是这样的,而不是per[bx] = a呢?是不是很奇怪;其实无论哪种都没有错,都可以将两者联系;但我们思考一下,如果总是固定的一个添加方法,如果出现子树远远大于根数,那么连接起来的情况就会很离奇。树的路径就会变得很大,因此我们自然会想到,那就把小的连接到大的不就好了;而Find()方法的效率取决于树的路径大小,那么我们不就又提高了代码的效率了吗?

void unionSet(int p, int q)  
{  
    int i = find(p);  
    int j = find(q);  
    if (i == j) return;  
    if (sz[i] < sz[j])//sz[]用来记录树的size;
         { id[i] = j; sz[j] += sz[i]; }  
    else 
        { id[j] = i; sz[i] += sz[j]; }  
    count--;  
}  
原文地址:https://www.cnblogs.com/7750-13/p/7263836.html