并查集(一)入门问题

参考文章:【算法与数据结构】—— 并查集_the_ZED的博客-CSDN博客

 

并查集(Union-find Sets)是一种非常精巧而实用的数据结构,它主要用于处理一些不相交集合的合并问题


原理大概

1.以集合中某个元素作为代表元代表一个集合。

2.集合中的所有数据以树状结构存储,根节点作为代表元。

3.对于查找某个元素所在集合,只需要一直向根节点遍历,查找代表元。

4.对于合并两个集合,只需要依照某个原则,将其中一个集合的代表元作为另一个代表元的父节点即可。


依照以上特性,常常用以辅助处理以下问题:

1.求解最小生成树的Kruskal算法。

2.维护无向图的连通性(判断两个点是否在同一连通块内,或增加一条边后是否会产生环);


模板题目

题一:

SCAU 18733 

齐齐哈尔oj

Description

操场上有好多好多同学在玩耍,体育老师冲了过来,要求他们排队。同学们纪律实在太散漫了,老师不得不来手动整队:
"A,你站在B的后面。"
"C,你站在D的后面。"
"B,你站在D的后面。哦,去D队伍的最后面。"

更形式化地,初始时刻,操场上有 n 位同学,自成一列。每次操作,老师的指令是 "x y",表示 x 所在的队列排到 y 所在的队列的后面,
即 x 的队首排在 y 的队尾的后面。(如果 x 与 y 已经在同一队列,请忽略该指令) 最终的队列数量远远小于 n,老师很满意。
请你输出最终时刻每位同学所在队列的队首(排头),老师想记录每位同学的排头,方便找人。

输入格式

第一行两个整数 n 和 m (n,m≤30000),紧跟着 m 行每行两个整数
x 和 y (1≤x,y≤n)。

输出格式

仅一行 n 个整数,表示每位同学所在队列排头同学的编号。

输入样例

5 4
1 2
2 3
4 5
1 3

输出样例

3 3 3 5 5

思路:

给定集合之间的连通关系,即集合之间的连通问题。

只需考虑每个队列的队首,即集合的根节点(代表元)。

可以看到一个并查集。

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>

//并查集优化查找
int Find(vector<int>& P, int x) {
	while (P[x]) {
		x = P[x];
	}
	return x;
}
int main() {
	ios::sync_with_stdio(false);
	int n,m; cin >> n>>m;
	vector<int>Uset(n + 1, 0);
	for (int i = 0; i < m;i++) {
		int x, y; cin >> x >> y;
		int n = Find(Uset,x);
		int m = Find(Uset,y);
		if (n == m) continue;
			Uset[n] = m;
	}
	for (int i = 1; i <= n;i++) {
		cout << Find(Uset,i) << " ";
	}

}

题二:

SCAU 18945 

美团2021校招笔试-编程题(通用编程试题,第2场)

Description

小团是美团外卖的区域配送负责人,众所周知,外卖小哥一般都会同时配送若干单,
小团在接单时希望把同一个小区的单子放在一起,然后由一名骑手统一配送。
但是由于订单是叠在一起的,所以,他归类订单时只能知道新订单和已有的某个订单的小区是相同的,
他觉得这样太麻烦了,所以希望你帮他写一个程序解决这个问题。
即给出若干个形如a b的关系,表示a号订单和b号订单是同一个小区的 ,
请你把同一个小区的订单按照编号顺序排序,并分行输出,优先输出最小的订单编号较小的小区订单集合。
订单的编号是1到n。(可能存在同时出现a b和b a这样的关系,也有可能出现a a这样的关系)

输入格式

输入第一行是两个正整数n,m,表示接受的订单数量和已知的关系数量。(1<=n,m<=10000)
接下来有m行,每行两个正整数a和b,表示a号订单和b号订单属于同一个小区(1<=a,b<=n),
a,b可能相同,也可能出现重复的关系


输出格式

输出第一行包含一个整数x,表示这些订单共来自x个不同的小区。
接下来的输出包含x行,每行表示输出若干个订单编号,表示这些订单属于同一个小区,按照订单编号升序输出。优先输出最小的订单编号较小的小区。

输入样例

5 5
1 2
2 2
3 1
4 2
5 4

输出样例

1
1 2 3 4 5

思路:

同题一

只是存储结构上小小变化一下,我觉得我这里的不太好,请指教


#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<map>
#include<set>
int Find(vector<int>&P,int x) {
	while (P[x]) {
		x = P[x];
	}
	return x;
}
int main() {
	ios::sync_with_stdio(false);
	map<int, set<int>>ans;
	int n, m; cin >> n >> m;
	vector<int>E(n + 1, 0);
	vector<int>Uset(n + 1, 0);
	map<int, int>tmp;

	for (int i = 0; i < m;i++) {
		int x, y; cin >> x >> y;
		int n = Find(Uset, x);
		int m = Find(Uset, y);
		if (n == m)continue;
		Uset[m] = n;	
	}

    //map分组处理输出
	for (int i = 1; i <= n;i++) {
		int head = Find(Uset,i);
		ans[head].insert(i);
	}
	cout << ans.size() << endl;
	for (auto i:ans) {
		tmp[*i.second.begin()] = i.first;
	}
	for (auto i:tmp) {
		for (auto j : ans[i.second]) {
			cout << j << " ";
		}
		cout << endl;
	}
	
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值