并查集:将N个不同的元素分成一组不相交的集合,开始时,每个元素就是一个集合,然后按规律将两个集合进行合并。
举例如:(1)现在有10个元素:0,1,2,3,4,5,6,7,8,9;分别将每个元素看成一个集合,则可将它们看作数组下标,数组里先存储-1,代表都为根,如:
(2)现在知道有元素下列不相交集合关系:
则在上面数组基础上创建这些集合关系,数组变为:
过程为:以第一个集合为例,6,7,8的根都为0;则在0的位置加上数组位置6,7,8的值-1,此时0位置变为-4,再将6,7,8位置值存储它们的根节点(当然它们是一个一个元素进行实现的),0根节点此时存储的值的绝对值则为这个集合元素的个数。剩下的集合思想相同。
3.此时已经创建好以上集合关系,若现在将以上集合关系改为相交,如下:
即把第二个集合连接在第一个集合上,0变为两个集合共同根节点,此时数组从2基础上变为:
过程则为:先找到两个集合的根节点,第1个集合根节点0位置值+=第2个集合根节点位置值,再将第二个集合位置值变为第一个根节点0,此时则将两个集合连接了起来。
这就为并查集的实现过程,可用其解决一道面试题朋友圈问题,题目如下:
假如已知有n个人和m对好友关系(存于数字r)。如果两个人是直接或间接的好友(好友的好友的好友…),则认为他们属于同一个朋友圈,请写程序求出这n个人里一共有多少个朋友圈。假如:n = 5,m = 3,r = {{1 , 2} , {2 , 3} , {4 , 5}},表示有5个人,1和2是好友,2和3是好友,4和5是好友,则1、2、3属于一个朋友圈,4、5属于另一个朋友圈,结果为2个朋友圈。
代码实现:
#include <iostream>
using namespace std;
//并查集解决朋友圈问题
class UnionSet
{
public:
UnionSet(size_t n)
:_n(n)
{
_parent=new int[n+1];
memset(_parent,-1,sizeof(int)*(n+1));//将数组初始化为-1
}
void Union(int r1,int r2)
{
int root1=Find(r1);
int root2=Find(r2);
if(root1!=root2)
{
_parent[root1]+=_parent[root2];
_parent[root2]=root1;
}
}
size_t Find(int index) //查找某一个朋友圈根节点
{
size_t root=index;
while(_parent[root]>0)
{
root=_parent[root];
}
return root;
}
size_t CountRoot()//统计根节点
{
size_t count=0;
for(size_t i=1;i<=_n;++i)
{
if(_parent[i]<0)
{
++count;
}
}
return count;
}
protected:
int* _parent;
size_t _n; //人的个数
};
size_t Friends(int arr[][2],int n,int m)
{
UnionSet un(n);
for(size_t i=0;i<m;++i)
{
un.Union(arr[i][0],arr[i][1]);
}
return un.CountRoot();
}
void TestUnionSet()
{
//int arr[][2]={{1,2},{2,3},{4,5}};
//int n=5;
//int m=3;
int arr[][2]={{1,2},{2,4},{3,5},{5,6},{4,7},{9,8},{8,9}};
int n=9;
int m=7;
size_t ret=Friends(arr,n,m);
cout<<"朋友圈个数为:"<<ret<<endl;
}
#include "UnionSet.h"
#include <cstdlib>
int main()
{
TestUnionSet();
system("pause");
return 0;
}