用故事讲并查集(简单易懂)

目录

    • 什么是并查集
    • 讲故事(拿上小板凳,认真听)
    • 运用并查集解题
    • 小结

认真看完,你真的可以掌握,而且忘了并查集,都很难。

什么是并查集

官方定义:并查集支持查找一个元素所属的集合以及合并两个元素各自所属的集合等运算。当给出两个元素的一个无序对(a,b),需要快速“合并”a和b所在的集合时,需要反复“查找”元素的集合,“并”、“查”和“集”三字由此而来。在这种数据类型中,n个不同的元素被分为若干组。每组是一个集合,这种集合叫分离集合,称之为并查集
如果上面看不懂的话,简单的说,并查集就是找朋友和认朋友的过程。咦,朋友又是从哪里冒出来的!!!请听下面的故事。

讲故事(拿上小板凳,认真听)

《小王子》是一本写给大人的童话,里面有句很经典的话“所有的大人都曾是小孩,虽然只有少数人记得”,使笔者想起许许多多的卡通人物,故事也就随之开始了。

相传在无忧无虑的卡通大陆上,生活着成千上百的卡通人物,有小猪佩奇,哆啦A梦,熊大熊二等等。桃花源般的生活并没有持续多久,暗夜笼罩大陆,大陆被注入了黑魔法,使得卡通人物们整天在外面游荡,碰到和自己不是一路人的,就免不了要打一架。幸运的是,他们心中的纯真还在,始终保持一个原则就是绝对不打自己的朋友,将“朋友的朋友就是我的朋友”奉为金科玉律。只要是能通过朋友关系串联起来的,不管拐了多少个弯,都认为是和自己一路的人。而不在同一个群落的人,无论如何都无法通过朋友关系连起来,于是就可以放心地大打一架。(附上朋友关系图)
用故事讲并查集(简单易懂)_第1张图片
但是两个原本互不相识的人,如何判断是否属于一个朋友圈,一个群落呢? 我们可以在每个朋友圈内选择出一个代表,作为这个朋友圈的队长。比如小猪佩奇所在的这个朋友圈中,我们可以选择猪爸爸为队长,每个圈子就可以这样命名“猪爸爸朋友之队”,“光头强朋友之队”……之后,如果两个人在外,遇到只要互相确定一下自己的队长是不是同一个人,就可以确定敌友关系了。

但是还有一个问题啊,大家只知道自己直接的朋友是谁,(比如猪妈妈的直接朋友是乔治,而图图一个直接朋友也没有)很多人可能根本就不认识自己圈子的队长。要判断自己的队长是谁,只能漫无目的一个个打电话问:“你是不是队长?你是不是队长?” 这样一来,效率太低,甚至可能陷入无限循环中。

于是队长下令,重新组队。队内所有人实行分等级制度,形成树状结构,我,队长就是根节点,下面分别是二级队员、三级队员…。每个人只要记住自己的上级是谁就行了。遇到判断敌友关系的时候,只要一层层向上问,直到最高层,就可以在短时间内确定队长是谁了。

因为我们关心的只是两个人之间是否连通,至于他们是如何连通的,以及每个圈子内部的结构是怎样的并不重要。所以我们可以放任队长随意重新组队,只要不搞错敌友关系就好了。

各个朋友圈在刚开始时,都只是孤家寡人(如下图)。每一个朋友圈就一个人,所以就选自己是队长。我们假设每一个人物,都有一个自己的编号(从1~n)我们需要定义一个数组int pre[n+1],表示当前人物的上级是谁。如果pre[i]=i.,就表示编号为i的人物就是这个朋友圈的队长。如果pre[10]=1,就表示编号为10的人物的上级是1
用故事讲并查集(简单易懂)_第2张图片

//进行初始化,刚开始的时候,每个人都是自己朋友圈的队长
void init(int n){//n表示在大陆,一共生活着多少卡通人物
	for(int i=1;i<=n;i++){
		pre[i]=i;
	}
}

那么,我们接下来先说说,找朋友(find)的过程。比如说在哆啦A梦朋友圈中有大雄,静香,哆啦A梦。我们假设关系是:哆啦A梦(队长)->大雄->静香。如果静香在外碰到光头强那她就要通过find函数来找队长。首先静香找到自己的上级是大雄,之后大雄找到自己的上级是哆啦A梦,这时哆啦A梦发现自己的pre[i]=i,表明自己就是队长。所以,哆啦A梦告诉大雄我是队长,大雄告诉静香哆啦A梦是队长,这就找到了队长。
用故事讲并查集(简单易懂)_第3张图片

//找队长喽
 int find(int x){//x表示哪个人要找队长
 	while(pre[x]!=x){ //如果x的上级不是他自己说明,还没有找到该朋友圈队长
 		x=pre[x];//委托x的上级,继续找队长
 	}
  	return x;//成功找到
}

我们现在在看一看,怎么将两个人"友"连接在一起?其实很简单,我们只要找到这两个人的队长,如果是不同的队长,我们就随便将一个队长归顺到另一个队长下,那么这个朋友圈就变得更大了。比如熊大在路上碰到大雄,发现2个队长不一样,哆啦A梦就拉光头强小队进了自己的朋友圈,如下图这种情况。

用故事讲并查集(简单易懂)_第4张图片

//合并朋友圈
void join(int x,int y){//编号 x 和 y,比如x熊大编号,y大雄编号
 	int fx=find(x);//x的队长 fx光头强编号
 	int fy=find(y);//y的队长 fy哆啦A梦编号
 	pre[fx]=fy;//哆啦A梦拉光头强团队进自己的朋友圈
 }

运用并查集解题

小故事之后,我们对并查集的三部分是不是已经过目不忘了 。那么我们来实战一下吧。
题目地址
https://pintia.cn/problem-sets/994805046380707840/problems/994805056736444416
用故事讲并查集(简单易懂)_第5张图片

用故事讲并查集(简单易懂)_第6张图片
代码思路很简单的

#include 
int pre[10001];
int leaders[10001];
void init(int n){
 for(int i=1;i<=n;i++){
  pre[i]=i;
 }
}
int find(int x){
 while(pre[x]!=x){ 
   x=pre[x];
  }
   return x;
}
void join(int x,int y){
 int fx=find(x);
  int fy=find(y);
  pre[fx]=fy;
}
int main(){
 init(10000);//假设最大人数是10000 
 int max_number=0;
 int n; 
 scanf("%d",&n);
 int m;
 int leader;
 int y;
 for(int i = 0;i

小结

通过上面的故事和例子,相信大家对并查集并不陌生了吧。当然,如果大家想继续深入学习并查集,还是需要不断学习。
(我是dz,一个还在学习路上的童鞋。第一篇文章,如果讲解有错,评论改正)

你可能感兴趣的:(用故事讲并查集(简单易懂))