我们先来看一道16年小米校招笔试题
假如已知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个朋友圈。
方法一:解决这个问题,我们一开始会想到set。
set1={1,2},set2={2,3},set3={4,5};
set1={1,2,3},set2={};
set3={4,5};
如果两个set集合中存在相同元素,就将元素全部放在第一个集合set1中(不会出现重复元素),第二个集合清空,继续这个过程。最后各个独立的集合不存在任何一个相同元素。然后统计不为空的集合的个数,即朋友圈的个数。
方法二:
利用位图实现:
方法三:
并查集实现
并查集定义:
实际上是一个数组,只是这个数组比较特殊,最开始将数组的每个数据看成一个单独的集合,用-1表示。然后根据要求合并,1作为组长,2,3都为组员,组长的元素为负,表示组员的个数(包括组长),即这个集合的元素总个数。最后找到该数组中负数的个数就是集合的个数,该题就得到解决了。
实现代码:
#pragma once
#include <iostream>
using namespace std;
#include <cstring>
class UnionFindSet{
public:
//无参构造函数
UnionFindSet()
:array(NULL)
, _size(0)
{}
//有参构造函数
UnionFindSet(int size)
:array(new int[size])
, _size(size + 1)
{
memset(array, -1, sizeof(size_t)*size);
}
//查找组长元素
int FindRoot(int child)
{
if (array[child] > 0)
{
return array[child];
}
else
{
return child;
}
}
//合并两个集合
void Union(int child1, int child2)
{
int root1 = FindRoot(child1);
int root2 = FindRoot(child2);
if (root1 != root2)
{
array[root1] += array[root2];//在root1的元素中加上root2中的元素个数为合并后集合元素总个数
array[root2] = root1;//将组长元素更改为root11
}
}
//集合个数
int Count()
{
int count = 0;
for (int i = 0; i < _size - 1; ++i)
{
if (array[i] < 0)
++count;
}
return count;
}
//某个集合中的元素个数
int Size(int child)
{
int root = FindRoot(child);
return -array[root];
}
private:
int* array;
size_t _size;
};
//朋友圈
void test()
{
UnionFindSet friends(5);
friends.Union(1, 2);
friends.Union(2, 3);
friends.Union(4, 5);
int friendsCount = friends.Count();
cout << "共有" << friendsCount << "个朋友圈" << endl;
int tmp = friends.Size(3);
cout << "3所在的集合有" << tmp << "个元素" << endl;
}
输出结果: