目录
并查集主要用途
可以很快的实现下面两个操作:
①合并两个集合
②查询两个元素是否在同一个集合当中
基本原理
以森林的方式来维护,初始化的时候每个节点作为一颗单独的树。对于一颗有根树而言,这棵树的根节点是唯一的,那么我们可以用根节点作为这棵树的标志,树根的编号就是整个集合的编号。用一个数组来存储某个节点的父节点。例如:定义一个数组father,father[x]就表示x的父节点。
如何初始化?
for (int i = 1; i <= n; i++)// 刚开始所有节点的父节点都指向本身
nums[i] = i
如何判断是否到达了树根?
if(father[x]==x) // 只有树根的父节点定义为自己的位置
如何求x所处在的集合编号?
while(father[x]!=x) // 只要存放的父节点和自身不相等,说明不是根节点
x=father[x]; // 然后x跑到父节点的位置,继续判断父节点的父节点
return x;
有两个集合father1,father2如何合并两个集合?
// 将father1的根节点的父节点指向father2的根节点。root表示根节点
father1[root1]=root2; // 原本father1的根节点的父节点是本身
优化方式
路径压缩
我们查找的时候可以发现,判断两个元素是否在同一个集合当中,只跟这两个元素所处的集合的根节点是否相同有关,而跟自己的父节点没有关系。那么我们能不能找到一种方法,在查找到根节点的同时将这条路径上所有节点的父节点都修改为根节点呢?这样下次查找的时候效率就高得多了。答案是肯定的。
public static int Find(int x) {// 查找某个数的根节点
// 路径压缩。优化过后所有子节点的父节点都指向根节点
if(father[x]!=x)// 如果这个点的父节点不等于自身
father[x]=Find(father[x]);// 跑到父节点进行同样的操作
return father[x];// 如果找到了根节点,将根节点的值一路覆盖到路径上所有点的父节点
}
按秩合并
通过观察树的结构我们可以发现,对于一棵树而言,查找这棵树中某一元素的根节点的时间跟这个元素到根节点的路径长度有关,路就越长,查找越慢。所以,当我们合并两个集合的时候,将高度低的树往高度高的树上插,可以提高效率。使用按秩合并需要维护一个额外的数组,这个数组存放这个元素对应的树的高度,初始化为1。
public static void Merge(int a, int b) {
//high数组表示对应节点的树的高度。例如:high[i]表示元素i所在的树的高度
// 按秩合并
a = Find(a);// 找到a元素的根节点
b = Find(b);// 找到b元素的根节点
if (a == b)// 如果两个元素已经在同一个集合中,直接返回
return;
if (high[a] > high[b]) {// 将高度低的插入到高度高的树上
father[b] = a;
} else {
father[a] = b;
if (high[a] == high[b])// 如果高度相等,随便选择一个即可
high[b]++;
}
}
练习例题:合并集合
java题解
import java.util.Scanner;
public class Main {
static int[] father = new int[100010];// 存放每个节点的父节点
static int[] high = new int[100010];// 存放这个元素对应的树的高度
public static int Find(int x) {// 查找某个数的根节点
// 朴素写法
// while (father[x] != x)// 不相等就继续找
// x = father[x];
// return x;
// 路径压缩。优化过后所有子节点的父节点都指向根节点,加快速度
if (father[x] != x)
father[x] = Find(father[x]);
return father[x];
}
public static void Merge(int a, int b) {
// 朴素写法
// father[Find(a)] = Find(b);// number1的根节点的父节点指向number2的根节点
// 按秩合并
a = Find(a);// 找到a元素的根节点
b = Find(b);// 找到b元素的根节点
if (a == b)// 如果两个元素已经在同一个集合中,直接返回
return;
if (high[a] > high[b]) {// 将高度低的插入到高度高的树上
father[b] = a;
} else {
father[a] = b;
if (high[a] == high[b])// 如果高度相等,随便选择一个即可
high[b]++;
}
}
public static void main(String args[]) {
Scanner input = new Scanner(System.in);
int n = input.nextInt();
int m = input.nextInt();
for (int i = 1; i <= n; i++) {// 初始化
father[i] = i;
high[i] = 1;
}
for (int i = 0; i < m; i++) {
String str = input.next();
int number1 = input.nextInt();
int number2 = input.nextInt();
if (str.equals("M"))
Merge(number1, number2);
else if (Find(number1) == Find(number2))
System.out.println("Yes");
else
System.out.println("No");
}
}
}
练习例题:连通块中点的数量
java题解
import java.util.Scanner;
public class Main {
static int[] nums = new int[100010];
static int[] count = new int[100010];// 记录这个节点所在的集合的节点个数
public static void Connect(int number1, int number2) {
int a = Find(number1);// number1的根节点
int b = Find(number2);// number2的根节点
if (a != b) {
nums[a] = b;// number1的根节点的父节点指向number2的根节点
count[b] += count[a];// 更新集合中点的个数
}
}
public static int Find(int number) {// 查找这个数的根节点
while (nums[number] != number)
number = nums[number];
return number;
}
public static void main(String args[]) {
Scanner input = new Scanner(System.in);
int n = input.nextInt();
int m = input.nextInt();
for (int i = 1; i <= n; i++) {
nums[i] = i;
count[i] = 1;// 初始化
}
for (int i = 0; i < m; i++) {
String str = input.next();
if (str.equals("C")) {
int number1 = input.nextInt();
int number2 = input.nextInt();
Connect(number1, number2);
} else if (str.equals("Q1")) {
int number1 = input.nextInt();
int number2 = input.nextInt();
if (Find(number1) == Find(number2))
System.out.println("Yes");
else
System.out.println("No");
} else {
int number = input.nextInt();
System.out.println(count[Find(number)]);
}
}
}
}