并查集_易懂

并查集主要用于处理不相交集合的合并问题。

并查集:将编号为1 ~ n的n个的对象划分为不相交集合,在每个集合中,选择其中某个元素代表所在集合,在这个集合中,并查集的操作有初始化、合并、查找。
(1)初始化:
定义数字int s[]是以结点i为元素的并查集,刚开始每个点属于独立的集合,且以元素的值表示它的集s[i]
(2)合并:
把两个集合合并为一个集合,以此形成一棵搜索树
(3)查找:
查找元素是一个递归的过程,直到元素的值和它的集相等就可以找到根结点的集。
(4)统计有多少个集
如果s[i] = i,这是一个根结点,是它所在集的代表,统计根结点的数量就是集的数量。
(5)压缩路径:
在查询过程中,查询元素i所属的集需要搜索路径找到根结点,返回的结果是根结点,这条搜索路径可能很长,如果在返回时顺便把i所属的集改成根结点,就能在O(1)时间内得到结果,此方法成为压缩路径,即将整个搜索路径上的元素所属的集全改为根结点。

//算法核心
int find(int x)
{
	if(x != s[x]) s[x] = find(s[x]);
	return s[x];
}

模板题

合并集合

一共有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≤105

输入样例:

4 5
M 1 2
M 3 4
Q 1 2
Q 1 3
Q 3 4

输出样例:

Yes
No
Yes
#include <iostream>
#include<string.h>
using namespace std;
const int N = 100010;
int n, m;
int s[N];
int find(int x)//返回父亲结点 + 路径压缩 
{
	if(s[x] != x) //s[x]不是x的父结点
		s[x] = find(s[x]);//路径压缩,回溯时全部指向一个父结点
	return s[x];//元素值=集值,返回x的父结点
}
int main() 
{
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n;  i ++ ) s[i] = i;

	while(m -- )
	{
	    char op[2];
	    int a, b;
		scanf("%s%d%d", op, &a, &b);//scanf读取字符数组用%s会避免读入空格和回车
	
		if(op[0] == 'M') s[find(a)] = find(b);//合并 让s[x] = y,就是y的父结点变为s[x];
		else
		{
			if(find(a) == find(b)) puts("Yes");//这里就用到了压缩路径
			else puts("No");
		}
	}
	return 0;
}

加深理解

举个简单例子,一群人在一起聊天,a认识b,b认识c,c认识d,输入你想让对方认识的两个人,比如a和d,如果他们之间有朋友联系,那么a可以和d打个招呼(“Nice to meet you !”),如果两个之间相互独立,输出"Sorry!I don’t know you!"

#include<iostream>
using namespace std;
const int N = 10010;
int s[N];
int find(int x)
{
	if(s[x] != x) s[x] = find(s[x]);
	
	return s[x];
}
int main() 
{
	int n, m;
	cin >> n >> m;//输入总人数和多少对认识的人
	for(int i = 1 ; i <= n; i ++ ) s[i] = i;
	
	while( m -- )
	{
		int x, y;
		cin >> x >> y;//比如a认识b
		s[find(x)] = find(y);//合并到一起
	}
	int a, b;
	cin >> a >> b;//输入你想让谁和谁打招呼
	if(find(a) == find(b)) cout << "Nice to meet you !";
	else cout << "Sorry!I don't know you!";
}

在这里插入图片描述


并查集简单题

Ubiquitous Religions(无处不在的宗教)

描述

当今世界上有太多不同的宗教,很难一一掌握。您有兴趣找出您大学中有多少不同宗教信仰的学生。

您知道您的大学中有n个学生(0 <n <= 50000)。向每个学生询问他们的宗教信仰是不可行的。此外,许多学生不愿意表达自己的信念。避免这些问题的一种方法是,问m(0 <= m <= n(n-1)/ 2)对学生,并询问他们是否信仰同一宗教(例如,他们可能知道他们是否都参加同一宗教)教会)。从这些数据中,您可能不知道每个人的信仰,但是您可以大致了解在校园中可以代表多少种宗教。您可以假设每个学生最多订阅一种宗教。
输入值

输入包含多种情况。每种情况都以指定整数n和m的行开头。接下来的m行分别由两个整数i和j组成,指定学生i和j信仰相同的宗教。学生从1到n编号。输入的结尾由其中n = m = 0的行指定。
输出量

对于每个测试用例,在一行上打印例号(以1开头),然后是大学学生所信奉的不同宗教的最大数量。
样本输入

10 9 
1 2 
1 3 
1 4 
1 5 
1 6 
1 7 
1 8 
1 9 
1 10 
10 4 4 
2 3 
4 5 
4 8 
5 8 
0 0

样本输出

Case 1: 1
Case 2: 7

思路:输入人数,开始每个人是一个集合,将信仰宗教相同的人合并,然后总集合数减去合并的就是学生信奉的不同宗教的最大数量

#include<iostream>
using namespace std;
const int N = 50050;
int s[N];
int n, m;
int find(int x)
{
	if(s[x] != x) s[x] = find(s[x]); 
	
	return s[x];
}
int main()
{
	int x, y;
	int cnt = 0;	
	while(scanf("%d%d", &n, &m))
	{
		if(n == 0 && m == 0) break; 
		cnt ++ ;
		int ans = 0;
		for(int i = 1; i <= n; i ++ ) s[i] = i;
		
		while(m -- )
		{ 
			scanf("%d%d",&x, &y);
			
			x = find(x);
			y = find(y);
			
			if(x != y)//xy属于不同集合 
			{
				s[x] = y;//合并 
				ans ++ ;
			}
		} 
		printf("Case %d: %d\n", cnt, n - ans);	
	}	
	return 0;
}

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值