LeetCode基础--Union Find(Disjoint Set)

Union Find,也叫作 Disjoint Set,中文通常译作 “并查集”。
Union Find 有两种操作:Union 和 Find,即“连接”和“查找”。

并查集用于:一个集合被分成几组的情况,集合中的每个数据只属于一个单独的组,无向图的连通分量就是这样一个例子。如下图:

LeetCode基础--Union Find(Disjoint Set)_第1张图片
1~9 的集合中的 3 个连通分量,或者说 3 个 Disjoint Set。

Disjoint Set 数据结构可以快速地判断集合中的两个元素是否属于同一个分组,(或者说判断 两个顶点是不是在一个连通分量中)。还可以快速地连通(unite)两个分组(或者说把两个连通分量连成一个连通分量)。

在最简单的形式中,集合就是一个整数数组,然后用另一个同样大小的数组表示各项的父元素。
如下图:

LeetCode基础--Union Find(Disjoint Set)_第2张图片
两棵树分别都是 disjoint set。

如果两个元素在同一棵树上,则属于同一个 disjoint set。
每个树的根结点叫作这个set的“代表”,每个set永远都有一个唯一的“代表”。如果第 i 个元素是“代表”,则 parent[i] = i。如果如果第 i 个元素不是“代表”,则可以顺着其父元素找到“代表”。也即 Find() 操作:

public int Find(int i) 
{
    if (Parent[i] == i) 
    {
        return i;
    }
    else 
    { 
        return Find(Parent[i]);
    }
}

而 Union 操作的入参是两个数值,分别用 Find() 操作找到这两个数值所在 set 的“代表”,然后把其中一棵树挂在另一棵树的 Root 结点上,如下图:

LeetCode基础--Union Find(Disjoint Set)_第3张图片
注意:哪棵树挂在哪棵树上是无所谓的。
所以也可以是下面这样:

LeetCode基础--Union Find(Disjoint Set)_第4张图片

Union 操作的实现如下:

public void Union(int i, int j) 
{
    // 先分别找到两个数值的“代表”
    int irep = Find(i);   
    int jrep = Find(j);

    // 将 i 的代表指向 j 的代表。
    Parent[irep] = jrep;
}

Disjoint Set 的数据类可以封装成这样:

public class DisjointSet 
{
    // 集合的元素数量.
    public int Count { get; private set; }
    // 集合的各元素数量的父元素.
    private int[] Parent;
    // 初始化集合.
    public DisjointSet(int count) 
    {
        this.Count = count;
        this.Parent = new int[this.Count];
        // 初始状态下,所有元素都是单独的set.
        for (int i = 0; i < this.Count; i++) 
        {
            this.Parent[i] = i;
        }
    }
}

还可以优化一下树的高度,有两种方式:

1、路径压缩:通过在Find过程中插入一个缓存机制

public int Find(int i) 
{
    if (Parent[i] == i) 
    {
        return i;
    }
    else 
    { 
        int result = Find(Parent[i]);
        // 可以通过把 i 移动到集合的“代表”下,以缓存结果 
        Parent[i] = result;
        return result;
    }
}

如下图:

LeetCode基础--Union Find(Disjoint Set)_第5张图片

2,Union by Rank
添加一个同样 size 的数组,叫作 rank,如果第 i 个元素就是“代表”,则 rank[i] 就是代表树的高度。

private int[] Rank;
public DisjointSet(int count) 
{
    this.Count = count;
    this.Parent = new int[this.Count];
    this.Rank = new int[this.Count];
    for (int i = 0; i < this.Count; i++) 
    {
        this.Parent[i] = i;
        this.Rank[i] = 0;
    }
}

前面提到过,当我们合并两个set或者说两棵树时,顺序是无所谓的,我们称丙个set是 左树 和 右树,如果 左树的 rank 小于 右树的 rank,那么最好把左树放在右树下,这样高度不变,反之一样。
如下图:

LeetCode基础--Union Find(Disjoint Set)_第6张图片

代码实现如下:

public void Union(int i, int j) 
{
    int irep = this.Find(i);
    int jrep = this.Find(j);
    irank = Rank[irep];
    jrank = Rank[jrep];
    if (irep == jrep){
        return;
    }
    if (irank <= jrank) 
    {
        this.Parent[irep] = jrep;

    }
    else if (jrank < irank) 
    {
        this.Parent[jrep] = irep;

    }
}

参考:

http://www.mathblog.dk/disjoint-set-data-structure/
https://www.hackerearth.com/zh/practice/notes/disjoint-set-union-union-find/
https://en.wikipedia.org/wiki/Disjoint-set_data_structure

你可能感兴趣的:(LeetCode)