#2020寒假集训#并查集入门(Union-Find)代码笔记

【并查集】从字面意思来看……

  • 并=合并,查=查询,集=集合
  • 所以这个算法要实现的功能就是——
  1. 把不同集合的元素放到同一集合内
  2. 查找判断两元素是否在同一集合内

【并查集】原理是什么呢?我们来看个小故事……

《背景》

从前有座山,山上有座庙,庙里…有两大祖先BM和KK!
他们不停地繁衍后代,但族谱只记录了父子关系
于是就产生了两大家族,每个人暂且只知道自己的爸爸是谁 father[i]数组
同一家族的都是亲人
有的时候为了扩大家族,拜了把子、认了爸爸的也算是亲人啦

现在要做的就是——

  1. 让不同家族的人成为亲人(并)
  2. 查找两个人是否是亲人(查)
  3. 所谓的(集)就是这些家族

跑出这座庙再处理这个问题的时候,还会有好多祖先,不止BM和KK哦
如果是被女娲捏土造人的,那他就是自己的祖先,比如BM和KK!

《找祖宗》
为了判断咱俩是不是同一家族的,只要判断咱俩的祖先是否一样就行了鸭
作为一个小辈,我真的好想好想好想知道哦自己的祖宗是谁鸭,呜呜呜
于是就有了 find函数
怎么找呢?我只知道我的爸爸是谁,那就只能一层一层问上去
一层一层的长链实在是太麻烦,如果只有一两层该多好 时间复杂度
既然大家都想知道祖先是谁,叫爸爸叫爷爷也差不多嘛哈哈哈
那么当我找到自己祖先的时候,我所有问过的爸爸们的爸爸都变成了祖先 递归
包括我自己喔~也就是我们一长链的人都变成祖先的儿子 路径压缩
所有在找到祖先的同时,也改变了爸爸 返回find()改变father[i]

《认爸爸拜把子》
如果BM和KK想成为一家人怎么办!很简单鸭~BM认KK当爸爸就好咯
BM和KK本身就是自己的祖先,但如果我想和糖糖想做一家人怎么办?!
这个时候我得找到我和糖糖的祖先,请我的祖先认糖糖的祖先当爸爸!
这样就有了 baba()函数
呜呜呜,虽然有点不好意思哈哈哈哈,但必须得说服祖先们呢!
这个过程就需要找到祖先,把祖先的爸爸进行更改 fx=find(我) father[fx]认爸爸

@如此一来@
【记录爸爸】用father[i]数组
【找到祖先】用find()函数 //注意路径压缩
【做一家人】用baba()函数 //注意调用了find()也压缩了路径
【判断亲属】用if判断两个find是否相等即可

路径压缩示意图(几个岗位的人找boss-find函数内进行)
#2020寒假集训#并查集入门(Union-Find)代码笔记_第1张图片

/*
@拓展补充@
其实除了“路径压缩”,还有很多种优化的方式,比如“按秩合并”
这个思路大致就是BM是四世同堂,KK是十世同堂(不要忘了BM和KK都是祖先) 这个时候要让BM和KK做父子,应当让BM认KK当爸爸
即高度小的合并到高度大的,否则高度大的合并后高度还会加一 但小的躲进大的怀抱,对小的而言,高度加一,但对整体最大的高度没变呢!
*/

/*
@简述优劣@
【路径压缩】平均时间复杂度较小,但递归容易爆栈,且会破坏树状内部结构
【按秩合并】不会破坏结构,不用递归,不会爆栈,但平均时间复杂度比路径压缩大
*/

现在所有的人都用int型代号表示,那么!快来写代码叭!(◕ᴗ◕✿) ☆(≧∀≦*)ノ

代码实现如下(int main()之上的部分可作为模板)

#include
#include//C++scanf/printf等的头文件
#include//C++cin/cout等的头文件 
#include
#include
#include
#include
#include
#include//万能头文件 
#include
using namespace std;
typedef long long ll;
const int maxn=1010;
int father[maxn];
int n=1000;
void init()//初始化自己的爸爸是自己 
{
	for(int i=1;i<=n;i++)
	{
		father[i]=i;
	}
}
int find(int x)//完成"查"的功能,找自己的祖先,同时可以用于if判断是否同祖先为亲人
{
	return x==father[x]?x:father[x]=find(father[x]);//返回的是x或者递归继续 
	/*
		?:是三目运算符 
		如果x的祖先就是自己,那就终止,x==x;
		如果x的祖先不是自己,我的爸爸就变成祖先
		因为find终止的时候是找到祖先
		那么一层一层递归回来x的一系列爸爸爷爷都等于了他们的祖先
		这个时候找到了x的祖先,同时也进行了 【路径压缩】
		一个长链也会被压缩成菊花图(两层)
	*/
}
void baba(int x,int y)//完成"并"的功能,让两个人做亲人 
{
	int fx=find(x);
	int fy=find(y);
	/*
		if(fx!=fy) father[fx]=fy;
		但没什么意义,如果==father[fx]也会等于find(x)
		find函数之后整条链都等于祖先了 
		所以可以直接变为下面的代码(等于的时候再自己等于操作一次而已) 
	*/
	father[fx]=fy;//拜把子认爸爸->让自己的祖先和别人的爸祖先拜把子 
}
int main()
{
	init();
	int x,y,op;
	printf("\n#提示:请输入\n\n1代表两元素合并\n\n2代表查询是否在同一组内\n\n再跟上需操作的两个元素\n\n");
	//op==1->合并(做家人);op==2->查询(判亲属) 
	while(~scanf("%d %d %d",&op,&x,&y))
	{
		if(op==1) baba(x,y);
		else
		{
			if(find(x)==find(y)) printf("\n查询结果:Yes\n\n");
			else printf("\n查询结果:No\n\n");
		}
	}
	return 0;
} 

运行结果如下
#2020寒假集训#并查集入门(Union-Find)代码笔记_第2张图片

你可能感兴趣的:(2020寒假集训,算法)