算法入门到进阶——并查集


基本概念

并查集(Disjoint Set)是一种非常精巧而且实用的数据结构,它主要用于处理一些不相交集合的问题。经典的例子有连通子图、最小生成二叉树Kruskal算法和最近公共祖先等。

通常用“帮派”的例子来说明并查集的应用背景。在一个城市中有n个人,它们分成不同的帮派;给出一些人的关系,例如1号和2号是好朋友,2好和3号是好朋友,那么他们就属于一个帮派;在分析完所有的朋友关系之后,问有多少帮派,每个人属于那个帮派。给出的n可能是10的6次方。

并查集将编号分别为1~n的那个对象划分为不相交集合,在每个集合中,选择其中某个元素代表表示所在的集合。在这个集合中,实现并查集的操作有初始化,合并,查找。

例题

有n个人一起吃饭,有些人互相认识。认识的人想坐在一起
,不想跟陌生人坐。例如A
认识B,B认识C,那么A,B,C会做在一张桌子上。
给出认识的人,问需要多少张桌子?

源码

#include<iostream>
using namespace std;
const int num_max = 1025;
int p[num_max];

void init_set(int n) {  //初始化集合,所有人都没有关系
	if (n > num_max) {
		printf("Data error\r\n");
		exit(-1);
	}
	for (int i = 1; i <n+1; i++) {
		p[i] = i;
	}
}

int find_set(int x) {//查看此人是否和其他人有关系
	return x == p[x] ? x : p[x];
}

void union_set(int x, int y) {//这两个有关系的人合并在一卓
	x = find_set(x);
	y = find_set(y);
	if (x != y) p[x] = p[y];//合并
}

int main(void) {
	int t, n, m, x, y;
	cout << "输入测试次数:";
	cin >> t;
	while (t--) {
		cout << "输入总人数:";
		cin >> n;
		cout << "输入有关系的人的对数:";
		cin >> m;
		init_set(n);//初始化集合
		for (int i = 0; i < m; i++) {
			cout << "输入有关系的人:";
			cin >> x >> y;
			union_set(x, y);
		}
		int res = 0;
		for (int i = 1; i < n + 1; i++) {
			if (p[i] == i) res++;
		}
		cout << "总共需要" << res << "桌";
	}
	system("pause");
	return 0;
}

运行结果

在这里插入图片描述

优化

在合并元素x和y时先搜索到他们的根结点,然后再合并这两个根结点,即把一个根结点的集改成另一个根结点。这两个根结点的高度不同,如果把高度较小的集合并到较大的集上,能减少树的高度。

源码

#include
using namespace std;
const int num_max = 1024;
int p[num_max + 1];
int height[num_max + 1];

void init_set(int n) {//初始化
if (n > num_max) {
cout << “Data error”;
exit(-1);
}
for (int i = 1; i <= n; i++) {
p[i] = i;
height[i] = 0;
}
}

int find_set(int x) { //查找是否有根结点
return x == p[x] ? x : p[x];
}

void union_set(int x, int y) {//优化合并
x = find_set(x);
y = find_set(y);

if (height[x] == height[y]) {
	height[y] = height[x] + 1;
	p[x] = p[y];
}
else if (height[x] < height[y]) {
	p[x] = p[y];
}
else {
	p[y] = p[x];
}

}

int main(void) {
int t, n, m, x, y;
cout << “输入测试次数:”;
cin >> t;
while (t–) {
cout << “输入总人数:”;
cin >> n;
init_set(n);//初始化
cout << “输入有关系的人的对数:”;
cin >> m;
for (int i = 0; i < m; i++) {
cout << “输入有关系的人:”;
cin >> x >> y;
union_set(x, y);
}
int res = 0;
for (int i = 1; i <= n; i++) {
if (i == p[i]) {
res++;
}
}
printf(“一共需要%d桌\r\n”, res);
}
system(“pause”);
return 0;
}

运行结果

在这里插入图片描述

再优化

将我们的查询方式进行优化,在上面的find_set函数中,查询元素i所属的集需要搜索路径找到根结点,返回的结果是根结点。这条搜索路劲可能很长。如果在返回的时候顺便把i所属的集改成根节点,那么下次搜索的时候就能在O(1)时间内得到结果了。

源码


#include<iostream>
using namespace std;
const int num_max = 1024;
int p[num_max + 1];
int height[num_max + 1];

void init_set(int n) {//初始化
	if (n > num_max) {
		cout << "Data error";
		exit(-1);
	}
	for (int i = 1; i <= n; i++) {
		p[i] = i;
		height[i] = 0;
	}
}

int find_set(int x) { //查找是否有根结点
	if (x != p[x]) {
		find_set(p[x]);  //路径压缩,所有的元素的值都改为其根结点
	}
	return p[x];
}

void union_set(int x, int y) {//优化合并
	x = find_set(x);
	y = find_set(y);

	if (height[x] == height[y]) {
		height[y] = height[x] + 1;
		p[x] = p[y];
	}
	else if (height[x] < height[y]) {
		p[x] = p[y];
	}
	else {
		p[y] = p[x];
	}
}

int main(void) {
	int t, n, m, x, y;
	cout << "输入测试次数:";
	cin >> t;
	while (t--) {
		cout << "输入总人数:";
		cin >> n;
		init_set(n);//初始化
		cout << "输入有关系的人的对数:";
		cin >> m;
		for (int i = 0; i < m; i++) {
			cout << "输入有关系的人:";
			cin >> x >> y;
			union_set(x, y);
		}
		int res = 0;
		for (int i = 1; i <= n; i++) {
			if (i == p[i]) {
				res++;
			}
		}
		printf("一共需要%d桌\r\n", res);
	}
	system("pause");
	return 0;
}

运行结果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jacky~~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值