靠左原则:左边的是团伙的头头
擒贼先擒王原则:让团伙之间的大BOSS自己决定谁归顺谁。
道上规矩:能找到自己BOSS的就提拔,学名“路径压缩”。
并查集算法:并查集通过一个数组来实现,其本质是维护一个森林。刚开始的时候,森林的每个点是孤立的,也可以理解为每个点就是一棵只有一个结点的树,之后通过一些条件,逐渐将这些树合并成一棵大树。其实合并的过程就是“认爹”的过程。在“认爹”的过程中,要遵守“靠左”原则和“擒贼先擒王”原则。在每次判断两个结点是否已经在同一棵树中的时候(一棵树其实就是一个集合),也要注意必须求其根源,中间的父亲节点(“小BOSS”)是不能说明问题的,必须找到其祖宗(树的根结点),判断两个结点的祖宗是否是否是同一个根结点才行。
解密犯罪团伙代码和注释:
#include <bits/stdc++.h>
using namespace std;
//这里是初始化,非常地
int f[1001] = {
0}, n, m, sum = 0;
void init(){
int i;
for (i = 1; i <= n; ++i)
f[i] = i;
return ;
}
//这是找爹的递归函数,直到找到祖宗为止,其实就是去找犯罪团伙的最高领导人。因为“擒贼先擒王”原则
int findfather(int v){
if(f[v] == v)
return v;
else{
//这里是路径压缩,每次在函数返回的时候,顺带把炉石遇到的人的“BOSS”改为最后找到的祖宗的编号,也就是犯罪团伙的最高领导人的编号。这样可以提高今后找到犯罪团伙的最高领导人(其实就是树的祖先)的速度。
f[v] = findfather(f[v]);//这里进行了路径压缩
return f[v];
}
}
//这里是合并两个子集合的函数
void merge(int v, int u){
int t1, t2;//t1,t2分别为v和u的大BOSS(首领),每次双方的会谈都必须是各自最高领导人才行
t1 = findfather(v);
t2 = findfather(u);
if(t1 != t2){
//判断两个结点是否在同一集合中,即是否为同一祖先
f[t2] = t1;//“靠左”原则,左边变成右边的BOSS。即把右边的集合作为左边集合的子集。
}
return ;
}
//请从此处开始阅读程序, 从主函数开始读程序是个好习惯
int main(){
int i, x, y;
cin >> n >> m;
init();//初始化是必须的
for (i = 1; i <= m; ++i){
//开始合并犯罪团伙
cin >> x >> y;
merge(x, y);
}
//最后扫描有多少个独立的犯罪团伙
for (i = 1;i <= n; ++i){
if(f[i] == i) sum++;
}
cout << sum << endl;
return 0;
}
并查集也称为不相交集数据结构。
其实树还有很多神奇的用法,比如:线段树、树状数组、Trie树(字典树)、二叉树搜索、红黑树(其实是一种平衡二叉树)等等。