先来看一道题:
假设一组有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;
}