1.并查集定义
并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。
2.代码模板
2.1 朴素并查集
int p[N]; //存储每个点的祖宗节点
// 返回x的祖宗节点
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ ) p[i] = i;
// 合并a和b所在的两个集合:
p[find(a)] = find(b);
2.2 维护size的并查集
int p[N], size[N];
//p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量
// 返回x的祖宗节点
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ )
{
p[i] = i;
size[i] = 1;
}
// 合并a和b所在的两个集合:
size[find(b)] += size[find(a)];
p[find(a)] = find(b);
2.3 维护到祖宗节点距离的并查集
int p[N], d[N];
//p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离
// 返回x的祖宗节点
int find(int x)
{
if (p[x] != x)
{
int u = find(p[x]);
d[x] += d[p[x]];
p[x] = u;
}
return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ )
{
p[i] = i;
d[i] = 0;
}
// 合并a和b所在的两个集合:
p[find(a)] = find(b);
d[find(a)] = distance; // 根据具体问题,初始化find(a)的偏移量
3.具体例题
3.1 合并集合
原题链接
一共有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”。
每个结果占一行。
思路:根据并查集的特点,将输入的数据放入并查集中,然后根据查询指令来输出结果。
import java.util.*;
class Main {
static int n,m;
static final int N = 100000;
static int[] p = new int[N];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
while (m-- > 0) {
String str = sc.next();
int a = sc.nextInt();
int b = sc.nextInt();
if (str.equals("M")){
merge(a,b);
}else {
query(a,b);
}
}
}
private static int find(int x) {
if (p[x] != x) {
p[x] = find(p[x]);
}
return p[x];
}
private static void merge(int a,int b) {
p[find(a)] = find(b);
}
private static void query(int a,int b) {
if (find(a) == find(b)){
System.out.println("Yes");
}else{
System.out.println("No");
}
}
}
3.2 Leetcode990
原题链接
给定一个由表示变量之间关系的字符串方程组成的数组,每个字符串方程 equations[i] 的长度为 4,并采用两种不同的形式之一:"a==b" 或"a!=b"。在这里,a 和 b 是小写字母(不一定不同),表示单字母变量名。
只有当可以将整数分配给变量名,以便满足所有给定的方程时才返回true,否则返回 false。
解法: 构建一个并查集,然后将equations数组里的方程merge一下放入并查集,如果是==则看作是连接两个节点的边,那么由于表示相等关系的等式具有传递性,a==b和b==c可以推出a==c。也就是所有相等的变量可以有相同的祖宗节点。如果是!=,同一个不等式中的变量不能属于一条边上,所以查找他们分别的祖宗节点,如果相等则为矛盾,输出false。
代码
class Solution {
static int[] p = new int[26];
public boolean equationsPossible(String[] equations) {
for (int i = 0;i < 26;i++) {
p[i] = i;
}
for (int k = 0;k < equations.length;k++) {
String str = equations[k];
int i = str.charAt(0) - 'a';
int j = str.charAt(3) - 'a';
if (str.charAt(1) == '=') {
merge(i,j);
}
}
for (int k = 0;k < equations.length;k++) {
String str = equations[k];
int i = str.charAt(0) - 'a';
int j = str.charAt(3) - 'a';
if (str.charAt(1) == '!') {
if (find(i) == find(j)) return false;
}
}
return true;
}
public static void merge(int i,int j) {
p[find(i)] = find(j);
}
public static int find(int x) {
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
}