一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情。
一、题目描述:
一共有 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,都要输出一个结果,如果 aa 和 bb 在同一集合内,则输出Yes,否则输出No。每个结果占一行。
数据范围
输入样例:
4 5 M 1 2 M 3 4 Q 1 2 Q 1 3 Q 3 4输出样例:
Yes No Yes
二、思路分析:
并查集是一个十分精炼的维护数据集合的一个数据结构。
它能快速的解决两个操作。
- 将两个集合合并。
- 询问两个元素是否在一个集合中。
基本原理:每一个集合用树来进行维护,树根的编号就是集合的编号,每个节点都用p[x]来存放它的父节点,如果p[x] = x说明他是根节点,p[x]则为该集合的编号
3大问题理解并查集:
问题一: 如何判断树根?
p[x] = x,则该节点为树根;p[x] != x,则该节点不为树根
问题二:如何判断x的集合编号(该节点所在集合)?
while(p[x] != x) x=p[x]
问题三:如何合并两个集合?
p[x] = y,x为一个集合的根节点,y为另一个集合的根节点
上面最耗时的是问题二步骤,为了减少该步骤的耗时,并查集进行了一个关键的优化 路径压缩,就是当一个点找到根节点的时候把路径上的所有点的父节点换为根节点。优化后并查集问题二时间复杂度近似为o(1)
三、AC 代码:
package acwing;
import java.util.Scanner;
public class acwing_836 {
static int n,m;
static int[] p = null;
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
n = in.nextInt(); m = in.nextInt();
p = new int[n+1];
// 初始化节点等于本身,意味着一开始每个节点都是单独的集合
for (int i = 1; i < n+1; i++) {
p[i] = i;
}
in.nextLine();
for (int i = 0; i < m; i++) {
String[] s = in.nextLine().split(" ");
if (s[0].equals("M")) {
p[find(Integer.parseInt(s[1]))] = find(Integer.parseInt(s[2]));
}else{
if (find(Integer.parseInt(s[1])) == find(Integer.parseInt(s[2]))) {
System.out.println("Yes");
}else{
System.out.println("No");
}
}
}
}
/** 核心代码,查找+路径压缩*/
private static int find(int parseInt) {
if (p[parseInt] != parseInt) {
p[parseInt] = find(p[parseInt]);
}
return p[parseInt];
}
}