数据结构------并查集


先来看一道题:
假设一组有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属于一个朋友圈.。则一共拥有两个朋友圈。
函数原型 :int friends(int m, int n, int* r[]);


这是2016年小米校招的一道笔试题,分值40!!!用我们下面讲解的这种数据结构会很容易拿到这40分,下来就来看看并查集的概念吧。

并查集

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

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

并查集的实现原理

并查集中需要两种数据类型的参数:集合名类型和集合元素的类型
在许多情况下,可以用整数作为集合名。如果集合中有n个元素,可以用0~n-1以内的整数来表示元素。实现并查集的一个典型方法是采用树形结构来表示元素及其所 属子集的关系。

用个实例来说明一下:

如全集合 r 为:r = {0,1,2,3,4,5 ,6,7,8 ,9 }。
首先并查集会开辟出一个10个元素大小的数组,每一个下标代表一个元素,这时每个元素自成一个集合,将每个元素都初始化为-1。这时大家都不相交,然后开始添加关系,我们添加上四个关系,它们都相当于 r 的子集合
r1 = {0,6,8}、 r2 = {1,2,9}、 r3 = {2,5}、r4={3,4,7}
看张图来理解一下:

这里写图片描述

添加r1={0,6,8}
如果几个独立的集合根据规则需要贵宾到一个集合中,我们可以先选取一个根(随便选一个即可),然后非根节点将自己的值累加到根节点上,并且把自己的值更新为根节点的坐标
这里写图片描述

添加r2= {1,2,9},原理同上
这里写图片描述

添加r3 = {2,5}
我们会发现,再添加r3的时候,2已经和1、9是一个集合了,这个时候我们就需要将5也并到以1为根的集合中。
这里写图片描述

添加r4={3,4,7}
这里写图片描述

在来说一种特殊例子,如果我们要将{6,7}合并呢?我们可以发现{0,6,8}和{3,4,7}是两个集合,所以两个集合需要合并,我们就需要将两个集合的根合并了,如下图:
这里写图片描述

说清楚了,来上代码:

#include <iostream>
using namespace std;
#include <stdlib.h>
#include <vector>
#include <assert.h>


class UnionFindSet
{
public:

    UnionFindSet(size_t size)
    {
        //在实际题目中0号一般没用,所以多开辟一个空间
        _set.resize(size + 1);
        for (int i = 0; i < size+1; i++)
            _set[i] = -1;
    }

    //合并集合
    void Union(int i, int j)
    {
        if (_set[i] < 0 && _set[j] < 0)
        {
            int root = i;
            _set[i] += _set[j];
            _set[j] = i;
        }
        else
        {
            int m = FindRoot(i);
            int n = FindRoot(j);

            _set[m] += _set[n];
            _set[n] = m;
        }
    }

    //找根节点
    int FindRoot(int i)
    {
        assert(i >= 0);

        int k = i;
        while (_set[k] >= 0)
            k = _set[k];
        return k;
    }

    //集合中有几个子集合
    int SetCount()
    {
        int count = 0;
        for (int i = 0; i < _set.size(); i++)
        {
            if (_set[i] < 0)
                count++;
        }
        return count - 1;
    }

private:
    vector<int> _set;
};

好了,我们就利用并查集来解一个我们最开始说的问题吧

int friends(int m, int n, int a[][2])
{
    UnionFindSet un(m);
    for (int i = 0; i < n; i++)
    {
        un.Union(a[i][0], a[i][1]);
    }

    return un.SetCount();
}

int main()
{
    int n = 5;
    int a[][2] = { { 1, 2 }, { 2, 3 }, { 4, 5 } };
    cout << "一共有" << friends(5, 3, a) << "个朋友圈" << endl;

    system("pause");
    return 0;
}

这里写图片描述

猜你喜欢

转载自blog.csdn.net/qq_34021920/article/details/80083925