洛谷 P3367- 并查集(Java模板)
Posted ZSYL
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了洛谷 P3367- 并查集(Java模板)相关的知识,希望对你有一定的参考价值。
概论
定义:
并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题(即所谓的并、查)。
比如,我们可以用并查集来判断一个森林中有几棵树、某个节点是否属于某棵树等。
主要构成:
并查集主要由一个整型数组pre[]
和两个函数find()
、join()
构成。
数组 pre[]
记录了每个点的前驱节点(父结点)是谁,函数 find(x)
用于查找指定节点 x
属于哪个集合,函数 join(x,y)
用于合并两个节点 x
和 y
。
加权标记法
路径压缩算法:加权标记法
主要思路:
加权标记法需要将树中所有节点都增设一个权值,用以表示该节点所在树中的高度(比如用rank[x]=3表示 x 节点所在树的高度为3)。
这样,在合并操作的时候就能通过这个权值的大小来决定谁做父结点。避免构造出来的树是连续一直向上的,便于优化查找效率。
举个例子,我们对以A,F为代表元的集合进行合并操作(如下图所示):
由于 rank(A) > rank(F)
,因此令 pre[F]= A
。合并后的图形如下图所示:
可以看出,合并前两个树的最大高度为3,合并后依然是3,这也就达到了我们的目的。但如果令pre[A]= F
,那么就会使得合并后的树的总高度增加。
我们常说,鱼和熊掌不可兼得,同理,时间复杂度和空间复杂度也很难兼得。由于给每个节点都增加了一个权值来标记其在树中的高度,这也就意味着需要额外的数据结构来存放权重信息,所以这将导致额外的空间开销。
总结
- 用集合中的某个元素来代表这个集合,则该元素称为此集合的代表元;
- 一个集合内的所有元素组织成以代表元为根的树形结构;
- 对于每一个元素 x,pre[x] 存放 x 在树形结构中的父亲节点(如果 x 是根节点,则令pre[x] = x);
- 对于查找操作,假设需要确定 x 所在的的集合,也就是确定集合的代表元。可以沿着pre[x]不断在树形结构中向上移动,直到到达根节点。
因此,基于这样的特性,并查集的主要用途有以下两点:
- 维护无向图的连通性(判断两个点是否在同一连通块内,或增加一条边后是否会产生环);
- 用在求解最小生成树的Kruskal算法里。
一般来说,一个并查集对应三个操作:
- 初始化( Init()函数 )
- 查找函数( Find()函数 )
- 合并集合函数( Join()函数 )
P3367 并查集
输入输出样例
输入
4 7
2 1 2
1 1 2
2 1 2
1 3 4
2 1 4
1 2 3
2 1 4
输出
N
Y
N
Y
参考代码:
import java.io.*;
import java.util.Arrays;
public class Main {
static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
public static void main(String[] args) {
int n = nextInt();
int m = nextInt();
int[] parents = new int[n+1];
int[] rank = new int[n+1];
Arrays.fill(parents, -1);
while (m-- > 0) {
int z = nextInt();
int x = nextInt();
int y = nextInt();
if (z == 1) {
union(x, y, parents, rank);
} else {
if (find(x, parents) == find(y, parents))
System.out.println("Y");
else
System.out.println("N");
}
}
}
// 判断元素是否属于同一集合,如果有公共父结点就在同一集合
public static int find(int x, int[] parents) {
int root = x;
while (parents[root] != -1) {
root = parents[root];
}
return root;
}
public static boolean union (int x, int y, int[] parents, int[] rank) {
int x_root = find(x, parents);
int y_root = find(y, parents);
if (x_root == y_root) {
return false;
} else if (rank[x_root] > rank[y_root]) {
parents[y_root] = x_root;
} else if (rank[x_root] < rank[y_root]) {
parents[x_root] = y_root;
} else {
parents[x_root] = y_root;
rank[y_root]++;
}
return true;
}
static int nextInt() {
try {
in.nextToken();
} catch (IOException e) {
e.printStackTrace();
}
return (int)in.nval;
}
}
P1551 亲戚
输入
6 5 3
1 2
1 5
3 4
5 2
1 3
1 4
2 3
5 6
输出
Yes
Yes
No
参考代码
import java.io.*;
import java.util.Arrays;
public class Main {
static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
public static void main(String[] args) {
int n = nextInt();
int m = nextInt();
int[] parents = new int[n+1];
int[] rank = new int[n+1];
Arrays.fill(parents, -1);
int p = nextInt();
while (m-- > 0) {
int x = nextInt();
int y = nextInt();
union(x, y, parents, rank);
}
while (p-- > 0) {
int x = nextInt();
int y = nextInt();
if (find(x, parents) == find(y, parents))
System.out.println("Yes");
else
System.out.println("No");
}
}
// 判断元素是否属于同一集合,如果有公共父结点就在同一集合
public static int find(int x, int[] parents) {
int root = x;
while (parents[root] != -1) {
root = parents[root];
}
return root;
}
public static boolean union (int x, int y, int[] parents, int[] rank) {
int x_root = find(x, parents);
int y_root = find(y, parents);
if (x_root == y_root) {
return false;
} else if (rank[x_root] > rank[y_root]) {
parents[y_root] = x_root;
} else if (rank[x_root] < rank[y_root]) {
parents[x_root] = y_root;
} else {
parents[x_root] = y_root;
rank[y_root]++;
}
return true;
}
static int nextInt() {
try {
in.nextToken();
} catch (IOException e) {
e.printStackTrace();
}
return (int)in.nval;
}
}
加油!
感谢!
努力!
以上是关于洛谷 P3367- 并查集(Java模板)的主要内容,如果未能解决你的问题,请参考以下文章