【数据结构】并查集入门例题讲解(c++代码逐句分析)

在平时写题的过程中,我们可能遇到这样一种题目----对于一个无向连通图,我们要询问2个点是否是连通的。在这个时候,我们就需要一个叫做并查集的数据结构来解决这种问题。
有关并查集的基本概念,在CSDN中有大佬的博客写的非常通俗易懂,CSDN博客:【算法与数据结构】—— 并查集(作者:酱懵静)大家如果在之前从来没有听说过并查集这个概念,那么建议在看完有关博客后再通过本博客的题目进行练习巩固。

题目情景引入 Acwing 合并集合

一共有 n 个数,编号是 1∼n,最开始每个数各自在一个集合中。

现在要进行 m 个操作,操作共有两种:

M a b,将编号为 a 和 b 的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;
Q a b,询问编号为 a 和 b 的两个数是否在同一个集合中;
输入格式
第一行输入整数 n 和 m。

接下来 m 行,每行包含一个操作指令,指令为 M a b 或 Q a b 中的一种。

输出格式
对于每个询问指令 Q a b,都要输出一个结果,如果 a 和 b 在同一集合内,则输出 Yes,否则输出 No。

每个结果占一行。

数据范围
1≤n,m≤1e5
输入样例:
4 5
M 1 2
M 3 4
Q 1 2
Q 1 3
Q 3 4
输出样例:
Yes
No
Yes

在这里可以告诉大家,这道题就是一道并查集模板题。所以说,这一道题的ac代码其实是要求背下来的。

#include<iostream>
#include<iomanip>
#include <utility>
#include<cstdio>
#include<string>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<map>
#include<stack>
#include<set>
#include<bitset>
#include<ctime>
#include<cstring>
#include<list>
#define ll long long
#define fi first
#define se second
using namespace std;
typedef  pair<int, int> PII;

const int N = 100050;
int n, m;
int p[N];//i的父亲是p[i],i=p[i]的是祖宗

int find(int x)//查找x的祖宗与路径压缩
{
	if (p[x] != x)p[x] = find(p[x]);//路径压缩代码,把这条路的所有后代变成祖宗的儿子
	return p[x];
}


void solve()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i++)//初始化,每个人自己就是自己家族的祖宗
	{
		p[i] = i;
	}
	while (m--)
	{
		char t;
		int a, b;
		cin >> t>>a>>b;
		if (t == 'M')
		{
			p[find(a)] = find(b);//给a的祖宗找个爹  --->  b的祖宗,那么a,b家族共同的祖先就是b了
		}
		else
		{
			if (find(a) == find(b))//如果祖宗相同
			{
				cout << "Yes" << endl;
			}
			else
			{
				cout << "No" << endl;
			}
		}
	}
}

int main()
{
	solve();
	return 0;
}

如果了解了并查集的概念的话,上面这道题的代码理解起来应该是不困难的。如果对上面的代码无法理解的话欢迎在下方评论留言提问。

在解决了并查集的最基本的问题后,我们需要进一步了解并查集的应用。

情景引入 Acwing 连通块中点的数量

给定一个包含 n 个点(编号为 1∼n)的无向图,初始时图中没有边。

现在要进行 m 个操作,操作共有三种:

C a b,在点 a 和点 b 之间连一条边,a 和 b 可能相等;
Q1 a b,询问点 a 和点 b 是否在同一个连通块中,a 和 b 可能相等;
Q2 a,询问点 a 所在连通块中点的数量;
输入格式
第一行输入整数 n 和 m。

接下来 m 行,每行包含一个操作指令,指令为 C a b,Q1 a b 或 Q2 a 中的一种。

输出格式
对于每个询问指令 Q1 a b,如果 a 和 b 在同一个连通块中,则输出 Yes,否则输出 No。

对于每个询问指令 Q2 a,输出一个整数表示点 a 所在连通块中点的数量

每个结果占一行。

数据范围
1≤n,m≤105
输入样例:
5 5
C 1 2
Q1 1 2
Q2 1
C 2 5
Q2 5
输出样例:
Yes
2
3

不难发现,这一题比起上一题增加了一个对于连通块中点的个数的操作。对此,我们的解决办法是----额外定义一个sum数组,对于sum[i],只有当i是这个家族的祖宗的时候才有意义,此时sum[i]代表i家族的成员的数量。因此我们把上一题的代码稍作修改就可以完成这道题目了。

#include<iostream>
#include<iomanip>
#include <utility>
#include<cstdio>
#include<string>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<map>
#include<stack>
#include<set>
#include<bitset>
#include<ctime>
#include<cstring>
#include<list>

#define ll long long
#define fi first
#define se second
using namespace std;
typedef  pair<int, int> PII;

const int N = 100050;
int n, m;
int p[N];
int sum[N];

/*int find(int x)
{
	if (x==p[x])
	{
		return x;
	}
	else
		return p[x] = find(p[x]);
}*/

int find(int x)
{
	if (p[x] == x)return x;
	else
		return p[x] = find(p[x]);
}


void solve()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		p[i] = i;
		sum[i] = 1;
	}
	
	while (m--)
	{
		string op;
		
		cin >> op;
		
		if (op == "C")
		{
			int a, b;
			cin >> a>>b;
			
			a = find(a);//首先用a和b的祖先来代替a,b
			b = find(b);
			if (a != b)//如果祖宗不同则代表不是一个家族,执行合并操作
			{
				p[a] = b;//让a家族的祖先认b家族的祖先做爹
				sum[b] += sum[a];//然后b家族的成员数加上所有a家族的成员数就是合并后大家族的人数,此时大家族的祖宗是b家族的祖宗
			}
			
			
		}
		else if (op == "Q1")
		{
			int a, b;
			cin >> a >> b;
			
			cout << (find(a) == find(b) ? "Yes" : "No") << endl;//祖宗不同就不是一个家族
			
		}
		else 
		{
			int a, b;
			cin >> a;
			cout << sum[find(a)] << endl;//用find找到祖宗,再通过sum[祖宗编号]来求出家族的人数(连通块的点的个数)
		}
	}
}

int main()
{
	solve();
	return 0;
}


上面的两道题目都是并查集的最基础的题目,适合刚刚学完并查集概念的同学用来练手。这两道题目的ac代码也是值得背下来的。

作者:Avalon Demerzel,喜欢我的博客就点个赞吧,更多图论与数据结构知识点请见作者专栏《图论与数据结构》

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值