什么是并查集:
并查集更像是一些元素的集合,我们把每个集合都用一棵树来表示。
并查集有什么用处:
1.将两个集合合并
2.询问两个元素是否在同一个集合中
优化后(路径压缩)可以在近乎O(1)的复杂度完成这两个操作。
基本原理:
集合是需要编号来告诉我们这些元素是属于哪个集合的,所以我们定义树根的编号就是整个集合的编号,我们也是用树的形式来维护每个集合的。用P[]数组储存父节点:p[x]储存x的父节点。
功能实现:
1.如何判断一个点是不是树根 ?
如果这个元素的父节点就是它本身,就代表这个点是树根。
if(p[x] == x);
代表x是根节点。
2.如何求x的集合编号?
while(p[x] != x){
x = p[x];
}
最后的x值就是它的集合编号。
3.如何合并两个集合?
假设px是x的集合编号,py是y的集合编号
p[x] = y;
进行两集合的合并 。
并查集的优化(路径压缩):
由于原先的查找操作(每一次都从x找x的父节点,再从x的父节点 找到上一层的父节点,直到找到根节点)与树高成正比,所以每一次查找都要一步步查找父节点,直到找到根节点。
所以现在进行优化(路径压缩):让每个子节点的父节点直接指向根节点。
详解实现:
1.初始化:
2.返回x的根节点+路径压缩(查找):
public static int find(int x){
if(p[x] != x){
p[x] = find(p[x]);
}
return p[x];
}
题目
题解完整代码(Java)
import java.util.Scanner;
public class Main {
static int N = 100010;
static int n;
static int[] p = new int[N];//存储每个节点的父节点
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
int m = sc.nextInt();
init();
while (m-- > 0){
String s = sc.next();
int a =sc.nextInt();
int b = sc.nextInt();
if(s.equals("M")){
p[find(a)] = find(b);//合并操作:让a的祖宗节点的父节点指向b的祖宗节点 ——> a集合并入b集合中
}else {
if(find(a) == find(b)){//查询a、b是否在同一集合内:如果a和b的祖宗节点是同一个的话,证明是同一个集合,否则,就不属于同一个集合
System.out.println("Yes");
}else {
System.out.println("No");
}
}
}
}
//初始化,一开始所有节点的父节点都指向自己
public static void init(){
for (int i = 1; i <= n; i++) {
p[i] = i;
}
}
//返回x的祖宗节点 + 路径压缩
public static int find(int x){
if(p[x] != x){//如果x的父节点不是它本身的话,证明x不是祖宗节点,就让x的父节点接着找父节点,直到找到祖宗节点。并且让x的父节点指向祖宗节点,返回祖宗节点。
p[x] = find(p[x]);
}
return p[x];
}
}
初步并查集学习中,先要了解并查集的功能及其实现,重点理解路径压缩。