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

一道笔试题


已知有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

1

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

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

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

上边的数组就会变为:
3

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

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[n])
    {
        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");
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值