一篇并查集 代码模板及经典例题

一、定义

并查集就是对集合进行合并及查询,包括两部分,并(union)和查(find)。 “并”是将有关联的元素合并为一个集合,“查”是查找这个元素属于哪个集合。

路径压缩:为了加快查找速度,查找时将x到根节点路径上的所有点的祖先设为根节点,该优化方法称为压缩路径
如从属关系A -> B -> C -> D,我们可以直接记A ->D,B -> D,C -> D,D -> D,即加快了从A访问祖先的速度。
路径压缩示例

二、万能模板
#include<iostream>
#include<vector>

using namespace std;
int n;
vector<int> father;

int find_loop(int n)	//循环,非递归
{
	while(n != father[n])
		n = father[n];
	return n;
}
int find(int n)		//递归型
{
	if(father[n] == n)
		return n;
	return find(father[n]);
}

int Find(int n)		//路径压缩型
{
	if(father[n] == n)
		return n;
	father[n] = Find(father[n]);	//直接将n的祖先指到根部
	return father[n];
}

void Union(int a,int b)		//集合,默认将a的祖先变为b的祖先
{
	int fa = find(a);
	int fb = find(b);
	father[fb] = fa;
}

int main()
{
	cin >> n;
	for(int i = 0;i < n;i++)
		father.push_back(i);	//初始化,最初将所有人的祖先定义为自己

	return 0;
}
三、用途

1.集合划分问题
2.领导关系、从属关系的判断
3.判断加一条边是否产生环
4.计算子图的数量

四、经典例题

1.P1551 亲戚
思路:很基础的并查集模板题,将有亲戚关系的人放在一个集合中,然后据题检查要判断的两个人的祖先是否相同即可。

#include<iostream>
#include<vector>

using namespace std;
int n,m,p;
vector<int> father;

int Find(int n)
{
	if(father[n] == n)
		return n;
	father[n] = Find(father[n]);
	return father[n];
}

void Union(int a,int b)
{
	int fa = Find(a);
	int fb = Find(b);
	father[fb] = fa;
}

int main()
{
	int a,b;
	cin >> n >> m >> p;
	for(int i = 0;i < n;i++)
		father.push_back(i);	//初始化

	for(int i = 0;i < m;i++)
	{
		cin >> a >> b;
		Union(a,b);	//合并
	}
	
	for(int i = 0;i < p;i++)
	{
		cin >> a >> b;
		if(Find(a) == Find(b)	//查找
			cout << "Yes";
		else
			cout << "No";
	}

	return 0;
}

2.P1536 村村通
思路:将相通的村子划成一个集合,然后只需修路将各集合连接起来即可实现所有村子互通,所以修路数为集合数减1 。

#include<iostream>
#include<vector>

using namespace std;
int n,m;
vector<int> father;

int Find(int n)
{
	if(father[n] == n)
		return n;
	father[n] = Find(father[n]);
	return father[n];
}

void Union(int a,int b)
{
	int fa = Find(a);
	int fb = Find(b);
	father[fb] = fa;
}

int main()
{
	int a,b;
	while(cin >> n && n && cin >> m)
	{
		int cnt = 0;
		father.clear();
		for(int i = 0;i <= n;i++)
			father.push_back(i);	//初始化
		for(int i = 0;i < m;i++)
		{
			cin >> a >> b;
			Union(a,b);	//合并
		}
		for(int i = 1;i <= n;i++)
			if(Find(i) == i)	//查找
				cnt++;

		if(cnt)
			cnt--;
		cout << cnt << endl;
	}
	
	return 0;
}

3.200 岛屿数量
题目大意:给定一个n行m列的海图,1代表陆地,0代表大海,所有上下左右相连的陆地称为同一个岛屿(斜向不算),求此海图的岛屿数量

思路:此题用dfs染色法可以解决,详见另一篇文章。但此处也可以用并查集解决,将所有连着的陆地合并成一个集合,然后只需要检查有多少个集合即可。但是问题来了,如何把二维的地图转换成一维的father数组呢,就只能用拼接的办法来将二维拼成一维的。例如mp[i][j]可以看成father[i*m+j]。

#include<iostream>
#include<vector>

using namespace std;
const int N = 110;
vector<int> father;
int n, m,cnt,mp[N][N];
int direction[4][2] = {{0,1},{1,0},{-1,0},{0,-1}};

int Find(int n)
{
	if(father[n] == n)
		return n;
	father[n] = Find(father[n]);
	return father[n];
}

void Union(int a,int b)
{
	int fa = Find(a);
	int fb = Find(b);
	father[fb] = fa;
}

int main()
{
	cin >> n >> m;
	for(int i = 0;i < n*m;i++)
		father.push_back(i);	//初始化
	for(int i = 0;i < n;i++)
		for(int j = 0;j < m;j++)
			cin >> mp[i][j];
			
	for(int i = 0;i < n;i++)
		for(int j = 0;j < m;j++)
			if(mp[i][j] == 1)	//如果是陆地
				for(int k = 0;k < 4;k++)	//上下左右扩展
				{
					int tx = i + direction[k][0];
					int ty = j + direction[k][1];
					if(mp[tx][ty] == 1 && tx >= 0 && ty >= 0 && tx < n && ty < m)	//扩展的点也是陆地且在地图内
						Union(i*m+j,tx*m+ty);	//合并
				}
	
	for(int i = 0;i < n;i++)
		for(int j = 0;j < m;j++)
			if(mp[i][j] == 1)
				if(Find(i*m+j) == i*m+j)	//查找
					cnt++;
	cout << cnt << endl;

	return 0;
}

4.冗余连接
题目大意:对一个有n个顶点的无环无向图进行加边操作,若加的边构成了一个环,则返回这条边。若有多个答案,返回最后一条边。

思路:用并查集判断连通性,加边前进行判断,若这两个顶点已属于一个集合,则加此边后就会构成环;若这两个点不属于同一个集合,则加边后会连通,不会成环,再将这两个点合并为同一个集合。

#include<iostream>
#include<vector>

using namespace std;
vector<int> father;
int n,m,ansa,ansb;

int Find(int n)
{
	if(father[n] == n)
		return n;
	father[n] = Find(father[n]);
	return father[n];
}

bool Union(int a,int b)
{
	int fa = Find(a);
	int fb = Find(b);
	father[fb] = fa;
	return (fa == fb);	//fa == fb代表a和b是同一个祖先,即在一个集合中
}

int main()
{
	int a,b;
	cin >> n >> m;		//n个顶点,m条边
	for(int i = 0;i < n;i++)
		father.push_back(i);	//初始化
	for(int i = 0;i < m;i++)
	{
		cin >> a >> b;
		if(Union(a,b))		//合并
			ansa = a,ansb = b;
	}

	cout << ansa << " " << ansb;

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值