Disjoint-Set Data Structure (Union Find Algorithm)

来源:互联网 发布:季节性过敏性鼻炎知乎 编辑:程序博客网 时间:2024/05/29 12:33

Disjoint-Set数据结构(Union Find Algorithm)

解释不相交数据结构的工作,并有效实施。

问题:我们有一些项目。我们被允许合并任何两个项目,以使它们相等。在任何时候,我们被允许询问两个项目是否被认为是平等的。

什么是Disjoint-Set?

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

Find:它确定特定元素在哪个子集中,并返回该特定集合的代表。该集合中的项目通常作为集合的“代表”。

Union:它将两个不同的子集合并成一个子集,一个集合的代表成为其他子集的代表。

Disjoint-Set还支持称为MakeSet的另一个重要操作,它创建一个仅包含给定元素的集合。

union-find如何工作?

通过比较两个查找操作的结果,我们可以确定两个元素是否在同一个子集中。如果两个元素在同一集合中,则它们具有相同的代表,而它们属于不同的集合。如果union被调用两个元素,我们合并两个元素所属的两个子集。

如何实现Disjoint集?

Disjoint-Set forests 是数据结构,其中每个集合由树数据表示,其中每个节点保存对其父节点的引用,并且每个集合的代表是该集合的树的根。

查找父节点直到到达根。

通过将一棵树的根连接到另一棵树的根部,将两棵树结合成一棵树。

例如,考虑如下图所示的由树表示的五个Disjoint-Set sets S1,S2,S3,S4和S5。每个集合最初只包含一个元素,因此它们的父指针指向自身或NULL。

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

元素i上的查找操作将返回Si的代表,其中1 <= i <= 5,即Find(i)= i

union-find

如果我们union(S3,S4),S3和S4将被合并成一个不相交的集合S3。现在
S1 = {1},S2 = {2},S3 = {3,4}和S5 = {5}。
Find(4)将返回S3的代表。即Find(4)= 3

union-find-1

如果我们联合(S1,S2),S1和S2将被合并成一个不相交的S1。现在
S1 = {1,2},S3 = {3,4}和S5 = {5}。
查找(2)或查找(1)将返回S1的代表。即Find(2)= Find(1)= 1

union-find-2

如果我们联合(S3,S1),S3和S1将被合并成一个不相交的集合S3。现在
S3 = {1,2,3,4}和S5 = {5}。

union-find-3

实现这些的一种方法可能是:
函数MakeSet(x)
x.parent = x

函数Find(x)

   if x.parent == x    return xelse    return Find(x.parent)
函数Union(x,y)
xRoot = Find(x)yRoot = Find(y)xRoot.parent = yRoot

以下是Union-Find的C ++实现 - 使用Hashtable实现Disjoint集合的。

#include <bits/stdc++.h>using namespace std;//表示一个不相交的集合class DisjointSet {    unordered_map<int, int> parent;public:    //执行MakeSet操作    void makeSet(vector<int> universe)    {          //创建n个不相交的集合(每个项目一个)        for (int i : universe)            parent[i] = i;    }    //找到元素k所属的集合的根    int Find(int k)    {         //如果k是根        if (parent[k] == k)            return k;        //递归为父,直到找到根        return Find(parent[k]);    }    //执行两个子集的联合    void Union(int a, int b)    {        //找到元素x和y所属的集合的根        int x = Find(a);        int y = Find(b);        parent[x] = y;    }};void printSets(vector<int> universe, DisjointSet &ds){    for (int i : universe)        cout << ds.Find(i) << " ";        cout << endl;}// 主函数int main(){       vector<int> universe = { 1, 2, 3, 4, 5 };    // 初始化DisjointSet class    DisjointSet ds;    //为universe 的每个元素创建单例集    ds.makeSet(universe);    printSets(universe, ds);    ds.Union(4, 3); // 4和3是同一组    printSets(universe, ds);    ds.Union(2, 1); // 1和2是同一组    printSets(universe, ds);    ds.Union(1, 3); // 1,2,3,4是同一组    printSets(universe, ds);    return 0;}

输出:

1 2 3 4 51 2 3 3 51 1 3 3 53 3 3 3 5

上述方法不比链表方法更好,因为它创建的树可能非常不平衡;然而,它可以通过两种方式增强。

第一种方式,称为union by rank,总是将较小的树附加到较大树的根。由于是影响运行时间的树的深度,所以较深的树的深度树被添加到较深的树的根之下,如果深度相等则仅增加深度。单元素树被定义为具有零等级,并且当相同秩r的两个树组合时,结果的等级为r + 1。对于联盟或查找操作,最坏的运行时间提高到O(log n)。

第二个改进,称为path compression,是一种在使用“查找”时平铺树的结构的方式。这个想法是在到根节点的路上访问的每个节点也可以直接附加到根节点;他们都有同样的代表。为了实现这一点,当Find以递归方式遍历树时,它会将每个节点的父引用更改为指向其找到的根。所产生的树更加平坦,加速未来的行动,不仅仅是对这些要素,而且直接或间接地引用它们。

改进的MakeSet和Union的伪代码:

function MakeSet(x)    x.parent = x    x.rank = 0function Union(x, y)    xRoot = Find(x)    yRoot = Find(y)    if xRoot == yRoot        return    // x and y are not already in same set. Merge them.    if xRoot.rank < yRoot.rank        xRoot.parent = yRoot    else if xRoot.rank > yRoot.rank        yRoot.parent = xRoot    else        yRoot.parent = xRoot        xRoot.rank = xRoot.rank + 1

这两种技术相辅相成,每次运行的运行时间实际上是一个小常数。

C ++实现 -

#include <bits/stdc++.h>using namespace std;//表示一个不相交的集合class DisjointSet {    unordered_map<int, int> parent;    //存储树的深度    unordered_map<int, int> rank;public:     //执行MakeSet操作    void makeSet(vector<int> universe)    {        //创建n个不相交的集合(每个项目一个)        for (int i : universe)        {            parent[i] = i;            rank[i] = 0;        }    }     //找到元素k所属的集合的根    int Find(int k)    {        //如果k不是根        if (parent[k] != k)            //路径压缩            parent[k] = Find(parent[k]);        return parent[k];    }     //执行两个子集的联合    void Union(int a, int b)    {        //找到元素x和y所属的集合的根        int x = Find(a);        int y = Find(b);        //如果x和y存在于同一集合中        if (x == y)            return;        //找到元素x和y所属的集合的根        if (rank[x] > rank[y])            parent[y] = x;        else if (rank[x] < rank[y])            parent[x] = y;        else        {            parent[x] = y;            rank[y]++;        }    }};void printSets(vector<int> universe, DisjointSet &ds){    for (int i : universe)        cout << ds.Find(i) << " ";    cout << endl;}// 主函数int main(){    // universe of items    vector<int> universe = { 1, 2, 3, 4, 5 };    // initalize DisjointSet class    DisjointSet ds;    // create singleton set for each element of universe    ds.makeSet(universe);    printSets(universe, ds);    ds.Union(4, 3); // 4和3是同一组    printSets(universe, ds);    ds.Union(2, 1); // 1和2是同一组    printSets(universe, ds);    ds.Union(1, 3); // 1,2,3,4是同一组    printSets(universe, ds);    return 0;}输出: 1 2 3 4 51 2 3 3 51 1 3 3 53 3 3 3 5

联合查找算法的应用:

1.实现Kruskal算法以找到图的最小生成树。

2.在非定向图中检测周期

原创粉丝点击