从文件夹的角度解读算法——并查集

1、并查集概念

根据百度百科的权威解释,并查集的定义大概如下所示:

并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。 这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。

并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。

2、文件与并查集的联系

(1)组合模式

说到树形结构,计算机里有一样众人皆知的东西——文件,其实就可以用树形结构来表示。

如果你上过设计模式这门课,GOF常用的23种设计模式中结构型模式类别中的组合模式,其中有一个经典的例子讲的就是文件。

那什么是组合模式呢,我们先来看看它的定义:

组合模式,将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。

发觉了没,这段定义里也有树形结构的关键字!并且它还有拓展,它说组合模式使得单个对象组合对象的使用具有一致性

那这里的“一致性”指的是什么呢,我们不妨来类比一下文件夹,比如,在下面这张图里,我在桌面上新建了一个文件夹,名称为Boss
在这里插入图片描述
我们现在单从这个文件夹来看的话,它可以看作是单个对象,因为这个文件夹是我刚创建的,里面啥也没有。

接下来我再在Boss文件夹下再创建一个文件夹,名字为boss:
在这里插入图片描述
现在我们再来看,Boss就不再是单个对象了,因为Boss文件夹下有子文件夹boss,所以Boss应该被看作是组合对象,而子文件夹boss是我们刚创建的,里面啥都没,所以它则因被看作是单个对象

因而现在的情况是:

单个对象 --》 boss文件夹
组合对象 --》Boss文件夹

现在我们再来看这句话,“组合模式使得单个对象组合对象的使用具有一致性”,现在我们这里单个对象是文件夹,组合对象也是文件夹,所以它们使用起来时,很显然,都是一样的方法,因而具有所谓的一致性

(2)文件夹查询

对于文件夹的查询,想必我们并不陌生吧?

对文件夹的查询常采用递归查询的方式。

比如现在我桌面上有一个文件夹1,文件夹1里有文件夹2,文件夹2里有文件夹3,文件夹3里有文件夹4,文件夹4里有文件夹5,因而针对文件夹5所在位置的相对路径是1->2->3->4
在这里插入图片描述
我如果想找到文件夹5,我就得依次打开文件夹1、文件夹2、文件夹3、文件夹4,才能找到文件夹5。

(3)文件夹合并

对于文件夹的合并,也非常好理解,比如我现在桌面上有Boss和1两个文件夹。
在这里插入图片描述
很明显,Boss有老大的意思,因而即使1的手下人多,Boss也不在怕的,因而1乖乖地臣服于了Boss,变成了Boss的小弟…

于是我直接把文件夹1丢进了Boss文件夹里

在这里插入图片描述
因而Boss文件夹最终便变成了以下的结构:
在这里插入图片描述
整体结构示意图为:
Boss → boss

1→2→3→4→5

3、并查集算法源码

(1)初始化

假设现在有n个文件夹,我们用一个数组f表示下标为i的文件夹的根文件夹,初始时每个文件夹的根文件夹为其自身

void initializeFolder(){
	for(int i = 1; i <= n; i++)
	f[i] = i;//每个文件夹的根文件夹为其自身
}

(2)查询

若我们需要查询文件夹x的根文件夹时:

int find(int x){
	if(f[x] == x) return x;//如果x的根文件夹就是其自身 
    return find(f[x]);  //否者递归查询找到x的根文件夹 	
}

(3)合并

当我们想把文件夹x所在的整个文件夹和y所在的整个文件夹合并时,我们需要需要先找到x的根文件夹a,再找到y的根文件夹b,再令a的根文件夹为b或令b的根文件夹为a即可。

void join(int x,int y){
	int a = find(x);//找到x的根文件夹a
	int b = find(y);//找到y的根文件夹b
	if(a != b)//如果它们两不是同一个文件夹
	f[a] = b;//令a的根文件夹为b
} 

(4)路径压缩

第(2)步查询的时候,存在一个问题:我们每次找一个文件夹x的根文件夹时,都不会把这次查询的情况记录下来,因此,当下次我们再来查一次的时候,就得进行重复的繁琐操作,如下图所示:
在这里插入图片描述
我们第一次为了找到文件夹5的根文件夹Boss,我们就得依次查找上级文件夹4->3->2->1->Boss,但为了避免下一次查找时也进行如此繁琐的操作,我们便可以在第一次找完后直接令5的根文件夹为Boss
在这里插入图片描述
经过这番操作后,下次我们再来找文件夹5的根文件夹的时候,就只用查一次就够了。

int find(int x){
	if(f[x] == x) return x;//如果x的根文件夹就是其自身 
	f[x] = find(f[x]);//递归查询找到x的根文件夹,并更新f[x] 
	return f[x];
}

(5)测试

#include<iostream>
using namespace std;
const int N = 5;
int f[N+1];
void initializeFolder(){
	for(int i = 1; i <= N; i++)
	f[i] = i;
}
int find(int x){
	if(f[x] == x) return x;//如果x的根文件夹就是其自身 
	f[x] = find(f[x]);//递归查询找到x的根文件夹 
	return f[x];
}
void join(int x,int y){
	int a = find(x);//找到x的根文件夹a
	int b = find(y);//找到y的根文件夹b
	if(a != b)//如果它们两不是同一个文件夹
	f[a] = b;//令a的根文件夹为b
} 
int main(){
	initializeFolder();//文件夹初始化 
	join(1,2);//令文件夹1的根文件夹为2 
	join(1,3);//令文件夹1的根文件夹为3,即让文件夹2的根文件夹为3 
	join(2,3);//令文件夹2的根文件夹为3 
	join(3,4);//令文件夹3的根文件夹为4 
	join(4,5);//令文件夹4的根文件夹为5
	//此时文件夹的目录级为5->4->3->2->1 
	cout << find(1) << endl;
	cout << find(2) << endl;
	cout << find(3) << endl;
	cout << find(4) << endl;
	cout << find(5) << endl;
}

运行结果:
在这里插入图片描述

发布了14 篇原创文章 · 获赞 11 · 访问量 3717

猜你喜欢

转载自blog.csdn.net/weixin_43715601/article/details/104078103