您的位置:首页 > 其它

朋友圈的“真相”—并查集(UnionFindSet)

2017-05-21 20:43 169 查看

一道笔试题

已知有n个人,m对好友关系(存于数字r),若两个人是直接或间接的好友(好友的好友的好友……),则认为他们同属于一个朋友圈。

例如:n = 5,m = 3,r={{1,2},{2,3},{4,5}};表示一共有5个人,3对好友关系,其中,1和2是好友,2和3是好友,4和5是好友;则1、2、3是一个朋友圈,4、5是一个朋友圈,结果是两个朋友圈。

问题:编写程序求出这n个人里有多少个朋友圈

对于这个问题,楼主刚开始是一脸懵逼的,但学了并查集之后,妈妈再也不用担心我不会啦^-^

并查集(UnionFindSet)

以上面这道题为基础,假设有这样一个数组,它由5个元素构成,初值全为-1



上面的下标就表示5个人。

接下来,我们用树状图来表示好友之间的关系:



如果我们把好友关系用数组元素来表示,2是1的好友,就把2下标处的数据叠加到1处,2处的数据存放1下标;3是2的好友,但2是1的好友,所以把3下标处的数据也叠加到1处,3处的数据存放2下标。4,5同理。

上边的数组就会变为:



由此我们可以总结出以下几点:

1.只要某下标处的数据是负数,它就是其所在朋友圈的“根”。负数的大小代表着朋友圈的人数,如下标1出的数据是-3,就说明此朋友圈有3个人。

2. 数组中有几个负数就说明有几个朋友圈,如该数组有2个负数,所以有2个朋友圈。

3.由各下标对应的数据可以找到“根”,有相同根的下标就在一个朋友圈中,如2和3拥有相同的根1,所以它们是一个朋友圈中的。

我们把上边的朋友圈换成“集合”,人换成集合中的“元素”,就可以得到并查集(UnionFindSet)的概念:

并查集就是将N个不同的元素分成一组不相交的集合;开始时,每个元素就是一个集合,然后按规律将两个集合进行合并。

按上边的例子可以理解为:开始时,每个人就是一个朋友圈,然后按照好友关系将人与人之间关联合并起来,形成最终的朋友圈。

有了并查集的概念,这道笔试题就很容易解决了。我们只需要建立一个并查集,然后实现判断接口,就可以Get到朋友圈的个数了。

Code

#include<iostream>
#include<Windows.h>

class UnionFindSet
{
public:
UnionFindSet(int n)
:_set(new int
)
{
for (int i = 0; i <n; ++i)
{
_set[i] = -1;
}
}

int FindRoot(int index)
{
int root = index;
while (_set[root] > 0)
{
root = _set[root];
}
return root;
}

void UnionPeople(int index1, int index2)
{
int root1 = FindRoot(index1-1);
int root2 = FindRoot(index2-1);

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

int CountCircleNumber(int n)
{
int count = 0;
for (int i = 0; i < n; ++i)
{
if (_set[i] < 0)
{
count++;
}
}
return count;
}

~UnionFindSet()
{
if (_set )
{
delete[] _set;
}
_set = NULL;
}
private:
int *_set;
};

int FriendCircle(int n, int m, int r[][2])
{
UnionFindSet f(n);
for (int i = 0; i < m; ++i)
{
f.UnionPeople(r[i][0], r[i][1]);
}

return f.CountCircleNumber(n);
}

int main()
{
//Test();
int r[][2] = { { 1, 2 }, { 2, 3 }, { 4, 5 } };
FriendCircle(5, 3, r);
system("pause");
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: