​​​​​​​不交集数据结构(联合查找算法)并查集

 

解释不相交的数据结构的工作原理并有效地实现它。

问题:我们有一些物品。我们被允许合并任何两个项目以将其视为相等。在任何时候,我们都可以问两个项目是否相等。

什么是不交集?

不相交集是一种数据结构,可跟踪一组元素划分为几个不相交(不重叠)的子集。换句话说,不相交集是一组集合,其中没有一个项目可以出现在多个集合中。它也称为联合查找数据结构,因为它支持对子集的联合和查找操作。让我们从定义它们开始:

查找:确定特定元素位于哪个子集中,并返回该特定集合的代表。该集合中的项目通常充当该集合的“代表”。

联合:将两个不同的子集合并为一个子集,并且一组的代表变为另一组的代表。

不相交集还支持另一个称为MakeSet的重要操作,该操作将创建一个仅包含给定元素的集合。

联合查找如何工作?

我们可以通过比较两个查找操作的结果来确定两个元素是否在同一子集中。如果这两个元素在同一集合中,则它们具有相同的表示形式;否则,它们属于不同的集合。如果在两个元素上调用联合,则合并两个元素所属的两个子集。

如何实现不相交集?

不相交集林是数据结构,其中每个集都由树数据表示,其中每个节点都拥有对其父级的引用,每个集的代表都是该集树的根。

  • 查找跟随父节点,直到到达根节点为止。
  • 联合通过将一棵树的根附加到另一棵树的根来将两棵树合并为一棵。

例如,考虑5点不相交的集合S1S2S3S4,和S5通过一个树来表示,如下面的图。每个集合起初仅包含一个元素,因此其父指针指向自身或NULL

S1 = {1}, S2 ={2}, S3 = {3}, S4 ={4} and S5 = {5}

Find对element的操作i将返回代表where的位置,即。Si1 <= i <= 5Find(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

联合查找算法-步骤1

如果我们这样做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

联合查找算法-步骤2

如果我们这样做Union (S3, S1)S3并且S1将合并为一个分离集,S3。现在,

S3 = {1, 2, 3, 4} and S5 = {5}

联合查找算法-步骤3

实现这些的一种方法可能是:

函数 MakeSet(x)
    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。对于UnionFind操作,最坏情况下的运行时间会提高到O(log(n))

2.第二种改进称为路径压缩,它是一种在每次使用Find时将其结构扁平化的方法。这个想法是,访问到根节点的每个节点也可以直接附加到根节点。他们都共享同一位代表。为此,在“查找”以递归方式遍历树时,它将更改每个节点的父引用以指向找到的根。生成的树更加扁平,不仅在这些元素上而且在直接或间接引用它们的元素上加快了将来的操作。

 
伪代码的改进MakeSetUnion

函数 MakeSet(x)
    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算法以找到图的最小生成树。

2.在无向图中检测周期

 
参考: https //en.wikipedia.org/wiki/Disjoint-set_data_structure

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值