不相交集合数据结构(并查集)小结

      不相交集合数据结构保持一组不相交的动态集合,集合中每一个元素有一个对象表示,或称其为代表。其主要操作有三个,

Make_Set(x)  ; 对集合x进行初始化,每一个元素就是一个对象。

Union(X,Y) ; 将包含集合X,Y的动态集合合并为一个新的集合;

Find_Set(x) ; 返回包含x的集合的代表;

不相交集合的一个应用,确定无向连通图中的连通子图的个数。可以采用链表形式,数组形式,和有根树的方式表示。

基本过程是 Connected_Components (G)  //G 表示无向图

 Connected_Components (G) 

{

  for  each vertex v ∈ V(G); // V(G) 表示G中的所有点定点

       do Make_Set(v);

  for each edge(u,v)∈ E(G); //E(G) 表示G中的所有边

    do if(Find_Set(u) != Find_Set(v))

       then Union(u,v);
}

链表表示法:

 用链表表示不相交集合是指 每一个集合都用一个链表来表示,每个链表的第一个对象就是作为它所在的集合的代表,

集合中的每一个对象包含一个集合成员,一个指向代表的指针,一个指向下一个集合成员对象的指针。

每个链表都有一个头指针head,和一个尾指针tail.

基本结构如下:

typedef struct Vertex  //链表对象
{
 int data;                          //集合成员
 struct Vertex *represent;  //集合的代表
 struct Vertex *next;         //指向下一个集合成员的指针
}Vex;
typedef struct    //链表集合
{
 Vex *head;
 Vex *tail;

 int  weight;  //按权值优化时使用
}DisSet[MAX_VERTEX_NUM + 1];  //MAX_VERTEX_NUM 表示最大的顶点数

void Make_Set(int x) //建立一个新的集合
{
   Vex *p;
     p = (Vex *)malloc(sizeof(Vex));
   p->represent = p;
   p->data = x;
   p->next = NULL;
   DSGraph[x].head = p;
   DSGraph[x].tail = p;

   DSGraph[x].weight  = 1; //按权值优化时使用
}
Vex * Find_Set(int x)  //返回一个指针,指向包含x的集合的代表
{
   Vex *p;
   p = DSGraph[x].head;
   return p->represent;
}
void Union_Set(int x, int y)  //将包含x ,y 的动态集合合并为一个集合
{
   Vex *p;
   if(Find_Set(x) != Find_Set(y))
   {
      DSGraph[x].tail->next = DSGraph[y].head;
      p = DSGraph[y].head;
      while(p->next)
      {
         p->represent = DSGraph[x].head->represent;
         p = p->next;
      }  
      p->represent = DSGraph[x].head->represent;
      DSGraph[x].tail = p;
   }
}
Connected_Components (G)  //G 即DSGraph 

{

  for each vertex v ∈ V(G); // V(G) 表示G中的所有点定点

   do Make_Set(v);

  for each edge(u,v)∈ E(G); //E(G) 表示G中的所有边

    do if(Find_Set(u) != Find_Set(v))

       then Union(u,v);
}

由于此方法效率不是很高,我们可以通过这个一种按权值优化的启发方式,其主要思想是在关于怎么合并,之前的链表表示,合并时,没有规定合并的顺序,

很有可能出现这样的情况,把一个大的集合与一个小的集合合并时,把大的集合的代表全部改为小的集合的代表,这样就会是的效率低下,当才用按权值合并时,

先比较权值,把权值小的集合的代表改为权值大的集合的代表,这样需要改的代表指针就会少很多,效率就会有所提高。

下面给出代码:

void Union_Set(int x, int y)  //将包含x ,y 的动态集合合并为一个集合
{
   int max,min,start;
   Vex *p,*q;
   if(Find_Set(x) != Find_Set(y))
   {
      p = Find_Set(x);
      q = Find_Set(y);
      if(DSGraph[p->data].weight < DSGraph[q->data].weight)
      {
         max = y;
         min = x;
         DSGraph[q->data].weight += DSGraph[p->data].weight;
         DSGraph[q->data].tail->next = DSGraph[p->data].head;
         start = q->data;
      }
      else
      {
         max = x;
         min = y;
         DSGraph[p->data].weight += DSGraph[q->data].weight;
         DSGraph[p->data].tail->next = DSGraph[q->data].head;
         start = p->data;
      }
      p = DSGraph[min].head;
      while(p->next)
      {
         p->represent = DSGraph[max].head->represent;
         p = p->next;
      }
      p->represent = DSGraph[max].head->represent;
      DSGraph[start].tail = p;
   }
}

数组方式 

void Disjoint_Set() //并查集
{
 int V[MAX_VERTEX_NUM + 1];
  part = 0;

  Min = vertex; //vertex 节点数,Min 连通子图数
 memset(V,0,sizeof(V));  //初始化 等同于Make_Set(x);
 for each edge(a,v)∈ E(G); //E(G) 表示G中的所有边

{

     if(V[a] == 0 && V[b] == 0)  //一条新边,化为同一个集合
    {
       part ++;
       V[a] = V[b] = part;
       Min --;
    }
    else
    {
       if(V[a] != 0 && V[b] == 0)  //一点已在某个集合中,将另一点也归为这个集合中
       {
          V[b] = V[a];
          Min --;
       }
       else
       {
          if(V[a] == 0 && V[b] != 0)   //一点已在某个集合中,将另一点也归为这个集合中
          {
             V[a] = V[b];
             Min --;
          }
          else
           {
             if(V[a] != V[b])    //两个店都已在某个集合中,且这两个集合不相同,需要合并。并修改集合代表
             {
                k = V[a];
                for(j = 1; j <= City; j ++)
                {
                   if(k == V[j])
                   {
                      V[j] = V[b];   //修改集合代表
                   }
                }
                Min --;
              }
            }
         }
      }
   }
}

此方法比链表好快,效率更高。

有根树 :

每棵树表示一个集合,树的每个节点都包含集合的一个成员,还有一个指向其父节点的“指针”,并且采用按秩合并和路劲压缩的优化。

效率最高。

Make_Set(x)   //初始化

     p[x] = x;   //表示x的父节点

     rank[x] = 0;  //表示x节点秩

Find_Set(x)    //路劲压缩  可以使得每一个节点直接指向根节点

  if(x != p[x])

          then  p[x] = Find_Set(p[x]);

      return p[x];

Union(x,y)    //按秩合并

     if(Find_Set(x) != Find_Set(y))  

       then u = Find_Set(x);

                  v = Find_Set(y);

                 if(rank[u] > rank[v])

                       then  p[v] = u;

          rank[u] += rank[v];

                  else

                        p[u] = v;

        rank[v] += rank[u];

Connected_Components (G)   

{

  for each vertex v ∈ V(G); // V(G) 表示G中的所有点定点

   do Make_Set(v);

  for each edge(u,v)∈ E(G); //E(G) 表示G中的所有边

    do if(Find_Set(u) != Find_Set(v))

       then Union(u,v);
}

posted @ 2011-07-21 10:05  ProgrammingEveryday  阅读(424)  评论(0)    收藏  举报