主要内容
假设有一群人,给出所有的交友关系(即哪两个人要交朋友),且朋友的朋友可以作为盟友,据此将这群人划分为不同的团体。
核心思想是用数字给这些人编号,然后将一个团体中的任意一人看作老大,其他人看作他的下属,检查两个人是否属于一个团体就看最上面的老大是不是相同的人。如果出现两个团体要合并时,就把其中一个团体的老大改成另一个老大的下属,如下图,2和4要交朋友,就让3变成0的下属,或者让0变成3的下属。下图是让0变成3的下属
图1
虽然是树状结构,但是由于上司不需要知道自己的下属是谁,只要让下属找得到自己的上司就行,所以用数组存储即可。下标表示对应编号的人,数组中存储每个人的上司是谁,如果要找老大,就一层一层地向上找。
我们用负数表示此人是一个团体的老大,所以数组初始化为-1,当两人要交友时,例如3和4要交友,将3视作老大,把下标4对应的元素-1加到3对应的元素上,然后再把下标4的元素改成3,这样不仅让4能找到自己的上司,还能统计小团体的人数。合并团体也是类似,不管是谁要交友,都看成两个老大在交友,然后还是同样的操作,如下。
图1在编号2与编号4交友前的数组:
交友后:
下面是对应的树状图
不难看出如果数据量大的话树可能会堆得很高,这样处于底层的人查找老大的效率就比较低下了。因此我们要将结构进行优化,尽量让所有的下属直接连接老大,如下。
只需在查找老大是谁时将下属连接到老大即可,因为每次输入交友关系时都会调用查老大的函数,而查找老大是通过递归查找的,将老大的编号返回时可以沿途将各个下属的上司都改成老大,尽管不能保证一定能变成上面这样的最优结构,但是已经非常简洁高效了。
参考代码
#include<vector>
using namespace std;
class Unionfindset{
public:
Unionfindset(size_t n)
: _ufs(n,-1)
{}
int Findroot(int x) {//找老大,返回老大的编号
if (_ufs[x] < 0) return x;
else return _ufs[x] = Findroot(_ufs[x]);//直接让下属连接老大,提高找老大的效率
}
void Union(int a, int b) {//交友、联合,将a看作上司
int ar = Findroot(a);
int br = Findroot(b);
if (ar != br) {
_ufs[ar] += _ufs[br];//算人数
_ufs[br] = ar;//认老大
}
}
size_t Setsize(int x) {//返回x所在团体的大小
return -_ufs[Findroot(x)];
}
size_t count() {//返回团体个数
size_t ans = 0;
for (auto e : _ufs) {
if (e < 0) ans++;
}
return ans;
}
private:
vector<int> _ufs;
};
如果只是写算法题,只写有用的功能即可