并查集(擒贼先擒王)

靠左原则:左边的是团伙的头头
擒贼先擒王原则:让团伙之间的大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树(字典树)、二叉树搜索、红黑树(其实是一种平衡二叉树)等等。

Guess you like

Origin blog.csdn.net/LXC_007/article/details/113887806