引入
最近在网上看到了这样一道面试题:
假如已知有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个朋友圈。
这里有很多的解决方案,但是最高效的应该还是使用并查集来处理。
简介
并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题。常常在使用中以森林来表示。
需要实现的操作有:合并两个集合,判断两个元素是否属于一个集合。
合并
合并操作很容易理解,找到要和合并的两个元素的根,把两个根结点连接在一起。如图所示:
判断两个元素是否在一个集合
这个操作可以转换为寻找两个元素的根结点,判断两个元素的根结点是否相同。
**本人不才,写不出特别生动的东西,这里有一篇大神的博文深入浅出的解释了各种并查集的操作。
http://blog.youkuaiyun.com/dellaserss/article/details/7724401**
解决方案
1.这里我们首先把5个人分别看成5个独立的个体,借用vector分别以这5个人的编号为下标(注意,下标从1开始),当他们相互独立时看为自成一个朋友圈,vector记为-1。
2.接着按照题目给出的1,2为朋友,2,3为朋友,4,5为朋友,把他们按照这个规则联合在一起,并把1中存的值和2中存的值相加放入根结点1,再把2的根结点下标放入2中。
例如1,2合并,则1位置存放-2,2位置存放1;
3.最后计算vector中小于0的数有多少就可以得到朋友圈的数量。
代码实现
#include<vector>
#include<iostream>
using namespace std;
class UnionSet
{
public:
UnionSet(size_t n)
{//构造函数,初始每个元素初始为-1
v.resize(n+1,-1);
}
int Find(size_t x)
{//查找x的根结点
size_t tmp = x;
while (v[tmp] >= 0)
{
tmp = v[tmp];
}
return tmp;
}
void Union(size_t x, size_t y)
{//合并两个元素
size_t root1 = Find(x);
size_t root2 = Find(y);
if (root1 == root2)
return;
v[root1] += v[root2];
v[root2] = root1;
}
size_t IsInSet(size_t x, size_t y)
{//判断两个元素是否在一棵树下
return Find(x) == Find(y);
}
size_t SetCount()
{//计算树的数量
size_t count = 0;
for (int i = 0; i < v.size(); ++i)
{
if (v[i] > 0)
count++;
}
return count-1;
}
private:
vector<int> v;
};
int Test()
{
int arr[3][2] = { {1,2},{2,3},{4,5} };
UnionSet U(5);
for (size_t i = 0; i < 3; ++i)
{
U.Union(arr[i][0], arr[i][1]);
}
cout << U.SetCount() << endl;
return 0;
}