解释不相交的数据结构的工作原理并有效地实现它。
问题:我们有一些物品。我们被允许合并任何两个项目以将其视为相等。在任何时候,我们都可以问两个项目是否相等。
什么是不交集?
不相交集是一种数据结构,可跟踪一组元素划分为几个不相交(不重叠)的子集。换句话说,不相交集是一组集合,其中没有一个项目可以出现在多个集合中。它也称为联合查找数据结构,因为它支持对子集的联合和查找操作。让我们从定义它们开始:
查找:确定特定元素位于哪个子集中,并返回该特定集合的代表。该集合中的项目通常充当该集合的“代表”。
联合:将两个不同的子集合并为一个子集,并且一组的代表变为另一组的代表。
不相交集还支持另一个称为MakeSet的重要操作,该操作将创建一个仅包含给定元素的集合。
联合查找如何工作?
我们可以通过比较两个查找操作的结果来确定两个元素是否在同一子集中。如果这两个元素在同一集合中,则它们具有相同的表示形式;否则,它们属于不同的集合。如果在两个元素上调用联合,则合并两个元素所属的两个子集。
如何实现不相交集?
不相交集林是数据结构,其中每个集都由树数据表示,其中每个节点都拥有对其父级的引用,每个集的代表都是该集树的根。
- 查找跟随父节点,直到到达根节点为止。
- 联合通过将一棵树的根附加到另一棵树的根来将两棵树合并为一棵。
例如,考虑5点不相交的集合S1
,S2
,S3
,S4
,和S5
通过一个树来表示,如下面的图。每个集合起初仅包含一个元素,因此其父指针指向自身或NULL
。
S1 = {1}, S2 ={2}, S3 = {3}, S4 ={4} and S5 = {5}
Find
对element的操作i
将返回代表where的位置,即。Si
1 <= i <= 5
Find(i) = i
如果我们这样做Union (S3, S4)
,S3
并且S4
将合并为一个分离集,S3
。现在,
S1 = {1}, S2 ={2}, S3 = {3, 4} and S5 = {5}
。
Find(4)
将返回set的代表S3
,即Find(4) = 3
如果我们这样做Union (S1, S2)
,S1
并且S2
将合并为一个分离集,S1
。现在,
S1 = {1, 2}, S3 = {3, 4} and S5 = {5}
。
Find(2)
或Find(1)
将返回set的代表S1
,即Find(2) = Find(1) = 1
如果我们这样做Union (S3, S1)
,S3
并且S1
将合并为一个分离集,S3
。现在,
S3 = {1, 2, 3, 4} and S5 = {5}
。
实现这些的一种方法可能是:
x.parent = x
函数 查找(x)
如果x.parent == x
返回x
else
返回Find(x.parent)
函数 Union(x,y)
xRoot =查找(x)
yRoot =查找(y )
xRoot.parent = yRoot
以下是union-find的C ++,Java和Python实现,该实现使用哈希表实现不相交集:
- C ++
- 爪哇
- Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
import java.util.HashMap;
import java.util.Map;
// A class to represent a disjoint set
class DisjointSet
{
private Map<Integer, Integer> parent = new HashMap();
// perform MakeSet operation
public void makeSet(int[] universe)
{
// create `n` disjoint sets (one for each item)
for (int i: universe) {
parent.put(i, i);
}
}
// Find the root of the set in which element `k` belongs
public int Find(int k)
{
// if `k` is root
if (parent.get(k) == k) {
return k;
}
// recur for the parent until we find the root
return Find(parent.get(k));
}
// Perform Union of two subsets
public void Union(int a, int b)
{
// find the root of the sets in which elements
// `x` and `y` belongs
int x = Find(a);
int y = Find(b);
parent.put(x, y);
}
}
class Main
{
public static void printSets(int[] universe, DisjointSet ds)
{
for (int i: universe) {
System.out.print(ds.Find(i) + " ");
}
System.out.println();
}
// Disjoint–Set data structure (Union–Find algorithm)
public static void main(String[] args)
{
// universe of items
int[] universe = { 1, 2, 3, 4, 5 };
// initialize `DisjointSet` class
DisjointSet ds = new DisjointSet();
// create a singleton set for each element of the universe
ds.makeSet(universe);
printSets(universe, ds);
ds.Union(4, 3); // 4 and 3 are in the same set
printSets(universe, ds);
ds.Union(2, 1); // 1 and 2 are in the same set
printSets(universe, ds);
ds.Union(1, 3); // 1, 2, 3, 4 are in the same set
printSets(universe, ds);
}
}
|
输出:
1 2 3 4 5
1 2 3 3 5
1 1 3 3 5
3 3 3 3 5
上面的方法并不比链接列表方法更好,因为它创建的树可能高度不平衡。但是,我们可以通过两种方式对其进行增强。
1.第一种方法称为“按等级联合”,是始终将较小的树连接到较大的树的根。由于影响运行时间的是树的深度,因此深度较小的树将添加到较深的树的根下,这只会增加深度相等的深度。单元素树的等级定义为零,每当两棵相同等级的树r
合并在一起时,结果的等级就为r+1
。对于Union或Find操作,最坏情况下的运行时间会提高到O(log(n))。
2.第二种改进称为路径压缩,它是一种在每次使用Find时将其结构扁平化的方法。这个想法是,访问到根节点的每个节点也可以直接附加到根节点。他们都共享同一位代表。为此,在“查找”以递归方式遍历树时,它将更改每个节点的父引用以指向找到的根。生成的树更加扁平,不仅在这些元素上而且在直接或间接引用它们的元素上加快了将来的操作。
伪代码的改进MakeSet
和Union
:
x.parent = x
x.rank = 0
函数 联合(x,y)
xRoot =查找(x)
yRoot =查找(y)
如果xRoot == yRoot
返回
//
x
并且y
不在同一个集合中。合并它们。如果xRoot.rank <yRoot.rank
xRoot.parent = yRoot
否则,如果xRoot.rank> yRoot.rank
yRoot.parent = xRoot
否则
yRoot.parent = xRoot
xRoot.rank = xRoot.rank +1
这两种技术是相辅相成的,并且每次操作的运行时间实际上是一个很小的常数。该算法可以在C ++,Java和Python中实现如下:
- C ++
- 爪哇
- Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
|
import java.util.HashMap;
import java.util.Map;
// A class to represent a disjoint set
class DisjointSet
{
private Map<Integer, Integer> parent = new HashMap();
// stores the depth of trees
private Map<Integer, Integer> rank = new HashMap();
// perform MakeSet operation
public void makeSet(int[] universe)
{
// create `n` disjoint sets (one for each item)
for (int i: universe)
{
parent.put(i, i);
rank.put(i, 0);
}
}
// Find the root of the set in which element `k` belongs
public int Find(int k)
{
// if `k` is not the root
if (parent.get(k) != k)
{
// path compression
parent.put(k, Find(parent.get(k)));
}
return parent.get(k);
}
// Perform Union of two subsets
public void Union(int a, int b)
{
// find the root of the sets in which elements
// `x` and `y` belongs
int x = Find(a);
int y = Find(b);
// if `x` and `y` are present in the same set
if (x == y) {
return;
}
// Always attach a smaller depth tree under the
// root of the deeper tree.
if (rank.get(x) > rank.get(y)) {
parent.put(y, x);
}
else if (rank.get(x) < rank.get(y)) {
parent.put(x, y);
}
else {
parent.put(x, y);
rank.put(y, rank.get(y) + 1);
}
}
}
class Main
{
public static void printSets(int[] universe, DisjointSet ds)
{
for (int i: universe) {
System.out.print(ds.Find(i) + " ");
}
System.out.println();
}
public static void main(String[] args)
{
// universe of items
int[] universe = { 1, 2, 3, 4, 5 };
// initialize `DisjointSet` class
DisjointSet ds = new DisjointSet();
// create a singleton set for each element of the universe
ds.makeSet(universe);
printSets(universe, ds);
ds.Union(4, 3); // 4 and 3 are in the same set
printSets(universe, ds);
ds.Union(2, 1); // 1 and 2 are in the same set
printSets(universe, ds);
ds.Union(1, 3); // 1, 2, 3, 4 are in the same set
printSets(universe, ds);
}
}
|
输出:
1 2 3 4 5
1 2 3 3 5
1 1 3 3 5
3 3 3 3 5
联合查找算法的应用:
1.实施Kruskal算法以找到图的最小生成树。
参考: https : //en.wikipedia.org/wiki/Disjoint-set_data_structure