并查集实现等价类

等价关系:集合或类(以集合为例)上的等价关系R指一个具有自反, 对称, 传递性的二元关系。
等价类: 在一个定义了等价关系的集合中可以按该等价关系分成等价类(即两个元素只要有xRy, 则它们属于同一等价类), 即集合的一些子集组成的集,。容易证明这些等价类两两不交且其并等于原集合.
 假设集合S有n个元素,m个形如(x,y) (x,y 属于 S)的等价偶对确定了等价关系R,如何求S的划分,即该如何求S的等价类?? 
 1)利用链表结构实现
 2)利用并查集实现(我么这里将讨论的)

并查集:并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。

并查集方式求等价类的方法如下:

    (1)令集合S中每个元素个子形成一个只包含单个成员的子集,记为S0,S1,...Sn-1.

      (2)  重复读入m个偶对,对每个读入的偶对(x,y),判定x和y所属子集。假设x属于Si,y属于Sj,如果Si != Sj,侧将Si 并于 Sj并将Si置空(或将Sj并于Si,                      并将Sj置空)。则当m个偶对都被处理过后,S0,S1,...,Sn-1中所有非空子集即为S的R等价类.


由此可见并查集方式求子集关键是1)查找一个元素所属的集合.2)求二个子集的合集3)求非空集合

并查集的定义

const int Max_TreeNode=100;//树的节点的最大值
//利用双亲表示法表示树

template <typename T> 
class UFSET
{
public:
	//树的节点定义
   typedef struct TreeNode
   {
     T data;//树的节点的值
     int parent;//节点的双亲所在的位置(如果该节点为根节点侧该值为以该节点为根的树的节点个数的负值)
   } TreeNode, *PTreeNode ;
    
    TreeNode nodes[Max_TreeNode];
	int n;//节点个数(集合元素的个数)
    
	UFSET(T * datas,int size):n(size)//datas为集合中所有元素值的集合,size为集合元素的个数
	{
		for(int i=0;i<size;i++)
		{
			nodes[i].data=datas[i];
			//最开始令每个元素各自形成只包含该元素的子集
			nodes[i].parent=-1;
		}
	}
};

      查找元素所属的集合
//查找元素e所属的集合(用根节点所在位置表示集合)
template <typename T>
int find(UFSET<T> & t,T e)
{
   //先找到值为e的节点
   for(int i=0;i<t.n;++i)
   {
    if(t.nodes[i].data==e)
	{  
	   int j;
       for(j=i;t.nodes[j].parent>=0;j=t.nodes[j].parent);
	   return j;
	}
   }
   return -1;//表示值为e的节点不存在
}

   当所查元素i不在树的第二层时,在算法中增加一个“压缩路劲的功能”,将所有从根到元素路径上的元素都变成树根的孩子。

  查找元素所属的集合的改进型

//find函数的一种改进型
template <typename T>
int mix_find(UFSET<T> & t,T e)
{
   //先找到值为e的节点
   for(int i=0;i<t.n;++i)
   {
    if(t.nodes[i].data==e)
	{  
	   int j;
       for(j=i;t.nodes[j].parent>=0;j=t.nodes[j].parent);
	   //将所有从节点t.nodes[i]到根t.nodes[j]的路径上的节点都变成根t.nodes[j]的孩子
       while(i!=j)
	   {
		  int temp=t.nodes[i].parent;
          t.nodes[i].parent=j;
		  i=temp;
	   }
	   return j;
	}
   }
   return -1;//表示值为e的节点不存在
}

合并子集

//合并子集
//将节点少的子集合并到节点多的子集上
template <typename T>
void merger(UFSET<T> & t,int i,int j)
{
   // t.nodes[i]和 t.nodes[j] 分别表示集合的两个互不相交的子集Si和Sj的根节点
   if(i<0 || i>t.n || j<0 || j>t.n)
	   return ;
   if(t.nodes[i].parent>t.nodes[j].parent)//t.nodes[i]的节点个数少于t.nodes[j]的节点个数
   {
      t.nodes[j].parent+=t.nodes[i].parent;
      t.nodes[i].parent=j;
   }
   else
   {
      t.nodes[i].parent+=t.nodes[j].parent;
      t.nodes[j].parent=i;
   }
}
 计算等价类
//计算等价类(即最后非空子集的个数)
template <typename T>
int EquClassCount(UFSET<T> & t)
{
	int count=0;
	for(int i=0;i<t.n;i++)
		if(t.nodes[i].parent<0)
			count++;
	return count;
}

下面求解小米公司2013笔试题:

朋友圈:假如已知有n个人和m对好友关系,如果两个人是直接或者间接有好友关系,则认为他们属于同一个朋友圈。写程序判断里面有多少朋友圈。
例如 
n = 5, m = 3  r = {(1,2), (2, 3), (4, 5)}  1 2 3 是一个朋友圈, 4 5 是一个朋友圈。
所以输出是2. 

完整代码余下所示:

#include "stdafx.h"
#include <iostream>
 
const int Max_TreeNode=100;//树的节点的最大值
//利用双亲表示法表示树

template <typename T> 
class UFSET
{
public:
	//树的节点定义
   typedef struct TreeNode
   {
     T data;//树的节点的值
     int parent;//节点的双亲所在的位置(如果该节点为根节点侧该值为以该节点为根的树的节点个数的负值)
   } TreeNode, *PTreeNode ;
    
    TreeNode nodes[Max_TreeNode];
	int n;//节点个数(集合元素的个数)
    
	UFSET(T * datas,int size):n(size)//datas为集合中所有元素值的集合,size为集合元素的个数
	{
		for(int i=0;i<size;i++)
		{
			nodes[i].data=datas[i];
			//最开始令每个元素各自形成只包含该元素的子集
			nodes[i].parent=-1;
		}
	}
};

//查找元素e所属的集合(用根节点所在位置表示集合)
template <typename T>
int find(UFSET<T> & t,T e)
{
   //先找到值为e的节点
   for(int i=0;i<t.n;++i)
   {
    if(t.nodes[i].data==e)
	{  
	   int j;
       for(j=i;t.nodes[j].parent>=0;j=t.nodes[j].parent);
	   return j;
	}
   }
   return -1;//表示值为e的节点不存在
}

//find函数的一种改进型
template <typename T>
int mix_find(UFSET<T> & t,T e)
{
   //先找到值为e的节点
   for(int i=0;i<t.n;++i)
   {
    if(t.nodes[i].data==e)
	{  
	   int j;
       for(j=i;t.nodes[j].parent>=0;j=t.nodes[j].parent);
	   //将所有从节点t.nodes[i]到根t.nodes[j]的路径上的节点都变成根t.nodes[j]的孩子
       while(i!=j)
	   {
		  int temp=t.nodes[i].parent;
          t.nodes[i].parent=j;
		  i=temp;
	   }
	   return j;
	}
   }
   return -1;//表示值为e的节点不存在
}

//合并子集
//将节点少的子集合并到节点多的子集上
template <typename T>
void merger(UFSET<T> & t,int i,int j)
{
   // t.nodes[i]和 t.nodes[j] 分别表示集合的两个互不相交的子集Si和Sj的根节点
   if(i<0 || i>t.n || j<0 || j>t.n)
	   return ;
   if(t.nodes[i].parent>t.nodes[j].parent)//t.nodes[i]的节点个数少于t.nodes[j]的节点个数
   {
      t.nodes[j].parent+=t.nodes[i].parent;
      t.nodes[i].parent=j;
   }
   else
   {
      t.nodes[i].parent+=t.nodes[j].parent;
      t.nodes[j].parent=i;
   }
}

//计算等价类(即最后非空子集的个数)
template <typename T>
int EquClassCount(UFSET<T> & t)
{
	int count=0;
	for(int i=0;i<t.n;i++)
		if(t.nodes[i].parent<0)
			count++;
	return count;
}

//将元素值为e1和e2所在集合合并
template <typename T>
void mix_merger(UFSET<T> & t,T e1,T e2)
{
	int i=mix_find(t,e1);
	int j=mix_find(t,e2);
	if(i>=0 && j>=0 && i!=j)
        merger(t,i,j);
}

int _tmain(int argc, _TCHAR* argv[])
{
	int friends[]={1,2,3,4,5};
	UFSET<int> friend_set(friends,sizeof(friends)/sizeof(int));
    mix_merger(friend_set,1,2);
    mix_merger(friend_set,2,3);
	mix_merger(friend_set,4,5);
	std::cout<<"朋友串个数为:"<<EquClassCount(friend_set)<<std::endl;
	system("PAUSE");
	return 0;
}

本文主要参考资料:数据结构(C语言本 严蔚敏版) P139——P143



你可能感兴趣的:(并查集,等价集合,朋友串问题,树的双亲表示法应用)