数据结构-并查集

并查集

写作目的

训练自己的Markdown绘图能力。

介绍

并查集是一种数据结构,体现的是森林(或者树)形态,主要操作有两种:

  • 并,意为合并,操作函数为void connect(int,int);(注:connect vt. & vi. 连接);
  • 查,意为查询:
    • 查询一个节点的父亲节点,查询函数为int find(int);(注:find v. 寻找);
    • 查询两个节点是否在用一个集合内,查询函数为bool search(int,int);(注:search v. 搜寻)。

具体思路与实现

首先,因为并查集是森林(或者树)的一种,所以我们可以用树的三种表示方法其中之一——父亲结点表示法(其它两种方法是儿子结点表示法儿子、兄弟表示法)。

定义数组ID[n](一般编程时数组下标从 1 1 1开始,应使用ID[n+1]),ID[i]表示第 i i i个结点当前状态下的父亲。

假设有 n n n个结点,为了方便,我们将每一个结点初始的父亲设为它自己。

代码实现如下:

#define n 100000//假设n=10^5

int ID[n+1];

void Init(void){//初始化函数
	register int i;
	for(i=1;i<=n;++i)
		ID[i]=i;
	return;
}

初始化后的示意图如下:

1
2
...
n

下面重点介绍如何查询一个节点的父亲节点。

int find(int);

不带路径压缩

find(x)表示x的父亲结点的编号。

代码实现如下:

int find(int x){
	if(x==ID[x])//如果x的父亲结点是自己
		return x;//返回x,停止递归
	else
		return find(ID[x]);//继续递归查找父亲结点
}

用图理解如下:

n = 5 n=5 n=5,此时并查集的状态如下:

1
2
3
4
5

运行find(5),过程如下:

开始
继续递归
继续递归
继续递归
返回1
返回1
返回1
结束 返回1
入口
运行5时5的父亲结点是3
运行3时3的父亲结点是2
运行2时2的父亲结点是1
运行1时1的父亲结点是自己

如果还不能理解的话,请手动模拟。

路径压缩

find(x)表示x的父亲结点的编号。

代码实现如下:

int find(int x){
	if(x==ID[x])//如果x的父亲结点是自己
		return x;//返回x,停止递归
	else
		return ID[x]=find(ID[x]);//继续递归查找父亲结点,并赋值以压缩路径
}

用图理解如下:

n = 5 n=5 n=5,此时并查集的状态如下:

1
2
3
4
5

运行find(5),过程如下:

开始
继续递归
继续递归
继续递归
返回1
返回1 并将2的父亲结点改为1
返回1 并将3的父亲结点改为1
结束 返回1 并将5的父亲结点改为1
入口
运行5时5的父亲结点是3
运行3时3的父亲结点是2
运行2时2的父亲结点是1
运行1时1的父亲结点是自己

运行过后,整个并查集的状态发生了改变,变成了这样:

1
2
3
4
5

在这次操作之后,再次调用find(5)只需要 O ( 1 ) O(1) O(1)的时间。

void connect(int,int);

在此之前,我们已经实现了int find(int);,那么其他的两个操作都比较简单,具体实现如下:

void connect(int a,int b){
	register int ra=find(a),rb=find(b);//寻找a和b的祖先节点,记为ra,rb
	if(ra!=rb)//假如a,b的祖先节点不同
		ID[rb]=ra;//把rb的父亲节点设为ra
	return;
}

bool search(int a,int b){
	return find(a)==find(b);//比较a,b的祖先节点是否相同
}

下面是一个具体的例子:

n = 6 n=6 n=6,此时并查集的状态如下。

ID[1]=1;
ID[2]=1;
ID[3]=2;
ID[4]=5;
ID[5]=5;
ID[6]=6;

用图表示为:

1
2
3
4
5
6

执行操作connect(4,6)

  1. 4 4 4的祖先是 5 5 5 6 6 6的祖先是它本身;
  2. 显然, 5 ≠ 6 5 \ne 6 5̸=6
  3. 所以把 6 6 6的祖先设置为 5 5 5
  4. 结束。

操作完成后:

ID[1]=1;
ID[2]=1;
ID[3]=2;
ID[4]=5;
ID[5]=5;
ID[6]=5;//主要操作

用图表示为:

1
2
3
4
5
6

执行操作connect(6,3)

  1. 6 6 6的祖先是 5 5 5 3 3 3的祖先是 1 1 1
  2. 显然, 5 ≠ 1 5 \ne 1 5̸=1
  3. 所以把 1 1 1的祖先设置为 5 5 5
  4. 结束。

操作完成后:

ID[1]=5;//主要操作
ID[2]=1;
ID[3]=1;//find()过程中进行了路径压缩
ID[4]=5;
ID[5]=5;
ID[6]=5;

用图表示为:

1
2
3
4
5
6

你可能感兴趣的:(数据结构-并查集)