问题:
假设已知有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个朋友圈。

可以用并查集解决。

一、什么是并查集

在一些应用问题中,需要将n个不同的元素划分成一组不相交的集合。开始时,每个元素自成一个单元素集合,然后按一定的规律将归于同一组元素的集合合并。在此过程中要反复用到查询某一个元素归属于那个集合的运算。
适合于描述这类问题的抽象数据类型称为并查集。

实现并查集的一个典型方法是采用树形结构来表示元素及其所属子集的关系。
每个集合以一棵树表示,树的每一个结点代表集合的一个单元素。所有各个集合的全集合构成一个森林,并用树与森林的父指针表示来实现。其下标代表元素名。第i个数组元素代表集合元素i 的树结点。树的根节点的下标代表集合名 称,根节点的父为-1,表示集合中的元素个数。

二、举例

假如全集合s 为:s = {0,1,2,3,4,5 ,6 , 7,8 ,9 },开始时,每个元素就是一个集合,如下:

小米面试题-朋友圈问题(并查集)_数组

第一步:初始化,我们用-1初始化每一个树的根结点

小米面试题-朋友圈问题(并查集)_结点_02

第二步:将集合中的元素合并成三个子集合,它们是全集合s 的子集合
s1 = {0,6, 7 , 8} ,s2 = {1,4, 9 } ,s3 = {2,3, 5 }。
其树形结构如下:

小米面试题-朋友圈问题(并查集)_数组_03

按照上面树的结构,假设我们的数组名为arr,我们需要更新数组的值:
(eg:a[0]=a[0]+a[6]+a[7]+a[8],所以为-4,a[6]=a[7]=a[8]=0(父节点))

以此类推,如下:

小米面试题-朋友圈问题(并查集)_并查集_04

根据上述的算法,数组中值的负数的绝对值表示的是集合中元素的个数;负数的个数表示集合的个数;非负的数字表示的其下标对应的父结点。

第三步:求并集

比如题目中:

小米面试题-朋友圈问题(并查集)_朋友圈_05

首先,我们应该判断,4或9和另一个集合中任意节点对应的父结点是不是同一个结点。
如果是同一个,则说明他们本身就在同一个集合中;
如果不是同一个,则先找到两个集合的父结点——0和1
再执行:
a[0]=a[0]-a[1]; a[1]=0(1的父结点);
此时数组变为:

小米面试题-朋友圈问题(并查集)_结点_06

三、代码

并查集:

#include<iostream>
#include<vector>
using namespace std;

class UnionFind
{
public:
UnionFind(size_t size)
:_set(size,-1) //用-1初始化
{
//_set.resize(size, -1);
//_set.assign(size, -1);
}

void Union(int a,int b) //求交集
{
int root1 = FindRoot(a);
int root2 = FindRoot(b);

if (root1 != root2)
{
_set[root1] += _set[root2];
_set[root2] = root1;
}
}

//合并后集合的个数
size_t Count()
{
size_t count = 0;
for (size_t i = 0; i < _set.size(); ++i)
{
if (_set[i] < 0)
{
count++;
}
}
return count;
}
private:
int FindRoot(int x) //查找父节点
{
while (_set[x] >= 0)
{
x = _set[x];
}
return x;
}
vector<int> _set;
};

接下来我们看上面的问题,可以这样解决:

size_t Friends(const int m, const int n, int arr[][2])
{
//因为题目给出的编号,为符合实际,所以我们不使用0号位置
UnionFind uf(m + 1);

for (int i = 0; i < n ; i++)
uf.Union(arr[i][0], arr[i][1]);

return uf.Count() - 1;

}
void UFTest()
{
const int m = 5;
const int n = 3;

int r[3][2] = { { 1, 2 }, { 2, 3 }, { 4, 5 } };
cout << "朋友圈个数:" << Friends(m, n, r) << endl;

}