📜  不相交集数据结构

📅  最后修改于: 2021-04-17 11:56:12             🧑  作者: Mango

考虑一个有很多人的情况,并要对他们执行以下任务。

  1. 添加一个新的友谊关系,即一个人x成为另一个人y的朋友。
  2. 查找x是否是y的朋友(直接或间接朋友)

例子:

We are given 10 individuals say,
a, b, c, d, e, f, g, h, i, j

Following are relationships to be added.
a <-> b  
b <-> d
c <-> f
c <-> i
j <-> e
g <-> j

And given queries like whether a is a friend of d
or not.

We basically need to create following 4 groups
and maintain a quickly accessible connection
among group items:
G1 = {a, b, d}
G2 = {c, f, i}
G3 = {e, g, j}
G4 = {h}

问题:查找x和y是否属于同一组,即查找x和y是否是直接/间接朋友。

解决方案:根据个人所属的群体将他们分成不同的集合。此方法称为不相交集数据结构,该结构维护不相交集的集合,每个集由其代表之一(代表其成员之一)表示。

方法:

  • 如何解决集合?最初,所有元素都属于不同的集合。在处理了给定的关系之后,我们选择一个成员作为代表。选择代表的方法有很多种,一种简单的方法就是选择索引最大的代表。
  • 检查2个人是否在同一组中?如果两个人的代表相同,那么他们将成为朋友。

使用的数据结构:
Array:一个整数数组,称为parent []。如果我们要处理n个项目,则数组的第i个元素表示第i个项目。更准确地说,数组的第i个元素是第i个项目的父项。这些关系创建一个或多个虚拟树。

树:它是一个不相交的集合。如果两个元素在同一棵树中,则它们在相同的不交集中。每棵树的根节点(或最高节点)称为集合的代表。每个集合始终只有一个唯一的代表。识别代表的简单规则是,如果i是集合的代表,则parent [i] = i。如果我不是他代表的那个代表,那么可以通过上树直到找到代表来找到它。

运作方式:

查找:可以通过递归遍历父数组直到我们遇到自身的父节点来实现。

// Finds the representative of the set  
// that i is an element of
int find(int i) 
{
    // If i is the parent of itself
    if (parent[i] == i) 
    {
        // Then i is the representative of 
        // this set
        return i;
    }
    else 
    {
        // Else if i is not the parent of 
        // itself, then i is not the 
        // representative of his set. So we 
        // recursively call Find on its parent
        return find(parent[i]);
    }
}

联合:将两个元素作为输入。然后使用find操作查找其集合的代表,最后将其中一棵树(代表该集合)放置在另一棵树的根节点下,从而有效地合并了这些树和集合。

// Unites the set that includes i 
// and the set that includes j
void union(int i, int j) 
{
    // Find the representatives
    // (or the root nodes) for the set
    // that includes i
    
    int irep = this.Find(i),

    // And do the same for the set 
    // that includes j    
    int jrep = this.Find(j);

    // Make the parent of i’s representative
    // be j’s  representative effectively 
    // moving all of i’s set into j’s set)
    this.Parent[irep] = jrep;
}

改进(按等级和路径压缩合并)
效率在很大程度上取决于树的高度。为了提高效率,我们需要最小化树的高度。我们可以按等级方法使用路径压缩和联合。

路径压缩(对find()的修改):它通过压缩树的高度来加快数据结构。可以通过在Find操作中插入一个小的缓存机制来实现。查看代码以获取更多详细信息:

// Finds the representative of the set that i
// is an element of.
int find(int i) 
{
    // If i is the parent of itself
    if (Parent[i] == i) 
    {
        // Then i is the representative 
        return i;
    }
    else
    { 
        // Recursively find the representative.
        int result = find(Parent[i]);

        // We cache the result by moving i’s node 
        // directly under the representative of this
        // set
        Parent[i] = result;
       
        // And then we return the result
        return result;
     }
}

按等级联合:首先,我们需要一个新的整数数组,称为rank []。该数组的大小与父数组相同。如果i是集合的代表,则rank [i]是表示集合的树的高度。
现在回想一下,在“联合”操作中,将两棵树中的哪棵移到另一棵树下都没关系(请参见上面的最后两个图像示例)。现在,我们要做的是最小化生成树的高度。如果我们将两棵树(或集合)结合在一起,我们将它们分别命名为左和右,那么这一切都取决于左树的等级和右树的等级。

  • 如果左方的等级小于右方的等级,则最好将左方移至右方,因为那不会改变右方的等级(而向右下方移动会增加高度)。同样,如果右方的等级小于左方的等级,那么我们应该在左下方向右移动。
  • 如果等级相等,那么哪棵树在另一棵树的下方都没关系,但是结果的等级将始终比树的等级大一。
// Unites the set that includes i and the set 
// that includes j
void union(int i, int j) 
{
    // Find the representatives (or the root nodes) 
    // for the set that includes i
    int irep = this.find(i);

    // And do the same for the set that includes j
    int jrep = this.Find(j);

    // Elements are in same set, no need to 
    // unite anything.    
    if (irep == jrep)
        return;

    // Get the rank of i’s tree
    irank = Rank[irep],

    // Get the rank of j’s tree
    jrank = Rank[jrep];

    // If i’s rank is less than j’s rank
    if (irank < jrank) 
    {
        // Then move i under j
        this.parent[irep] = jrep;
    } 

    // Else if j’s rank is less than i’s rank
    else if (jrank < irank) 
    {
        // Then move j under i
        this.Parent[jrep] = irep;
    } 

    // Else if their ranks are the same
    else
    {

        // Then move i under j (doesn’t matter
        // which one goes where)
        this.Parent[irep] = jrep;

        // And increment the result tree’s 
        // rank by 1
        Rank[jrep]++;
    }
}
C++
// C++ implementation of disjoint set
#include 
using namespace std;
class DisjSet {
    int *rank, *parent, n;
  
public:
    // Constructor to create and
    // initialize sets of n items
    DisjSet(int n)
    {
        rank = new int[n];
        parent = new int[n];
        this->n = n;
        makeSet();
    }
  
    // Creates n single item sets
    void makeSet()
    {
        for (int i = 0; i < n; i++) {
            parent[i] = i;
        }
    }
  
    // Finds set of given item x
    int find(int x)
    {
        // Finds the representative of the set
        // that x is an element of
        if (parent[x] != x) {
  
            // if x is not the parent of itself
            // Then x is not the representative of
            // his set,
            parent[x] = find(parent[x]);
  
            // so we recursively call Find on its parent
            // and move i's node directly under the
            // representative of this set
        }
  
        return parent[x];
    }
  
    // Do union of two sets represented
    // by x and y.
    void Union(int x, int y)
    {
        // Find current sets of x and y
        int xset = find(x);
        int yset = find(y);
  
        // If they are already in same set
        if (xset == yset)
            return;
  
        // Put smaller ranked item under
        // bigger ranked item if ranks are
        // different
        if (rank[xset] < rank[yset]) {
            parent[xset] = yset;
        }
        else if (rank[xset] > rank[yset]) {
            parent[yset] = xset;
        }
  
        // If ranks are same, then increment
        // rank.
        else {
            parent[yset] = xset;
            rank[xset] = rank[xset] + 1;
        }
    }
};
  
int main()
{
    DisjSet obj(5);
    obj.Union(0, 2);
    obj.Union(4, 2);
    obj.Union(3, 1);
    if (obj.find(4) == obj.find(0))
        cout << "Yes\n";
    else
        cout << "No\n";
    if (obj.find(1) == obj.find(0))
        cout << "Yes\n";
    else
        cout << "No\n";
  
    return 0;
}


Java
// A Java program to implement Disjoint Set Data
// Structure.
import java.io.*;
import java.util.*;
  
class DisjointUnionSets {
    int[] rank, parent;
    int n;
  
    // Constructor
    public DisjointUnionSets(int n)
    {
        rank = new int[n];
        parent = new int[n];
        this.n = n;
        makeSet();
    }
  
    // Creates n sets with single item in each
    void makeSet()
    {
        for (int i = 0; i < n; i++) {
            // Initially, all elements are in
            // their own set.
            parent[i] = i;
        }
    }
  
    // Returns representative of x's set
    int find(int x)
    {
        // Finds the representative of the set
        // that x is an element of
        if (parent[x] != x) {
            // if x is not the parent of itself
            // Then x is not the representative of
            // his set,
            parent[x] = find(parent[x]);
  
            // so we recursively call Find on its parent
            // and move i's node directly under the
            // representative of this set
        }
  
        return parent[x];
    }
  
    // Unites the set that includes x and the set
    // that includes x
    void union(int x, int y)
    {
        // Find representatives of two sets
        int xRoot = find(x), yRoot = find(y);
  
        // Elements are in the same set, no need
        // to unite anything.
        if (xRoot == yRoot)
            return;
  
        // If x's rank is less than y's rank
        if (rank[xRoot] < rank[yRoot])
  
            // Then move x under y  so that depth
            // of tree remains less
            parent[xRoot] = yRoot;
  
        // Else if y's rank is less than x's rank
        else if (rank[yRoot] < rank[xRoot])
  
            // Then move y under x so that depth of
            // tree remains less
            parent[yRoot] = xRoot;
  
        else // if ranks are the same
        {
            // Then move y under x (doesn't matter
            // which one goes where)
            parent[yRoot] = xRoot;
  
            // And increment the result tree's
            // rank by 1
            rank[xRoot] = rank[xRoot] + 1;
        }
    }
}
  
// Driver code
public class Main {
    public static void main(String[] args)
    {
        // Let there be 5 persons with ids as
        // 0, 1, 2, 3 and 4
        int n = 5;
        DisjointUnionSets dus = 
                new DisjointUnionSets(n);
  
        // 0 is a friend of 2
        dus.union(0, 2);
  
        // 4 is a friend of 2
        dus.union(4, 2);
  
        // 3 is a friend of 1
        dus.union(3, 1);
  
        // Check if 4 is a friend of 0
        if (dus.find(4) == dus.find(0))
            System.out.println("Yes");
        else
            System.out.println("No");
  
        // Check if 1 is a friend of 0
        if (dus.find(1) == dus.find(0))
            System.out.println("Yes");
        else
            System.out.println("No");
    }
}


Python3
# Python3 program to implement Disjoint Set Data
# Structure.
  
class DisjSet:
    def __init__(self, n):
        # Constructor to create and
        # initialize sets of n items
        self.rank = [1] * n
        self.parent = [i for i in range(n)]
  
  
    # Finds set of given item x
    def find(self, x):
          
        # Finds the representative of the set
        # that x is an element of
        if (self.parent[x] != x):
              
            # if x is not the parent of itself
            # Then x is not the representative of
            # its set,
            self.parent[x] = self.find(self.parent[x])
              
            # so we recursively call Find on its parent
            # and move i's node directly under the
            # representative of this set
  
        return self.parent[x]
  
  
    # Do union of two sets represented
    # by x and y.
    def Union(self, x, y):
          
        # Find current sets of x and y
        xset = self.find(x)
        yset = self.find(y)
  
        # If they are already in same set
        if xset == yset:
            return
  
        # Put smaller ranked item under
        # bigger ranked item if ranks are
        # different
        if self.rank[xset] < self.rank[yset]:
            self.parent[xset] = yset
  
        elif self.rank[xset] > self.rank[yset]:
            self.parent[yset] = xset
  
        # If ranks are same, then move y under
        # x (doesn't matter which one goes where)
        # and increment rank of x's tree
        else:
            self.parent[yset] = xset
            self.rank[xset] = self.rank[xset] + 1
  
# Driver code
obj = DisjSet(5)
obj.Union(0, 2)
obj.Union(4, 2)
obj.Union(3, 1)
if obj.find(4) == obj.find(0):
    print('Yes')
else:
    print('No')
if obj.find(1) == obj.find(0):
    print('Yes')
else:
    print('No')
  
# This code is contributed by ng24_7.


C#
// A C# program to implement  
// Disjoint Set Data Structure.
using System;
      
class DisjointUnionSets 
{
    int[] rank, parent;
    int n;
  
    // Constructor
    public DisjointUnionSets(int n)
    {
        rank = new int[n];
        parent = new int[n];
        this.n = n;
        makeSet();
    }
  
    // Creates n sets with single item in each
    public void makeSet()
    {
        for (int i = 0; i < n; i++)
        {
            // Initially, all elements are in
            // their own set.
            parent[i] = i;
        }
    }
  
    // Returns representative of x's set
    public int find(int x)
    {
        // Finds the representative of the set
        // that x is an element of
        if (parent[x] != x)
        {
              
            // if x is not the parent of itself
            // Then x is not the representative of
            // his set,
            parent[x] = find(parent[x]);
  
            // so we recursively call Find on its parent
            // and move i's node directly under the
            // representative of this set
        }
        return parent[x];
    }
  
    // Unites the set that includes x and
    // the set that includes x
    public void union(int x, int y)
    {
        // Find representatives of two sets
        int xRoot = find(x), yRoot = find(y);
  
        // Elements are in the same set, 
        // no need to unite anything.
        if (xRoot == yRoot)
            return;
  
        // If x's rank is less than y's rank
        if (rank[xRoot] < rank[yRoot])
  
            // Then move x under y so that depth
            // of tree remains less
            parent[xRoot] = yRoot;
  
        // Else if y's rank is less than x's rank
        else if (rank[yRoot] < rank[xRoot])
  
            // Then move y under x so that depth of
            // tree remains less
            parent[yRoot] = xRoot;
  
        else // if ranks are the same
        {
            // Then move y under x (doesn't matter
            // which one goes where)
            parent[yRoot] = xRoot;
  
            // And increment the result tree's
            // rank by 1
            rank[xRoot] = rank[xRoot] + 1;
        }
    }
}
  
// Driver code
class GFG 
{
    public static void Main(String[] args)
    {
        // Let there be 5 persons with ids as
        // 0, 1, 2, 3 and 4
        int n = 5;
        DisjointUnionSets dus = 
                new DisjointUnionSets(n);
  
        // 0 is a friend of 2
        dus.union(0, 2);
  
        // 4 is a friend of 2
        dus.union(4, 2);
  
        // 3 is a friend of 1
        dus.union(3, 1);
  
        // Check if 4 is a friend of 0
        if (dus.find(4) == dus.find(0))
            Console.WriteLine("Yes");
        else
            Console.WriteLine("No");
  
        // Check if 1 is a friend of 0
        if (dus.find(1) == dus.find(0))
            Console.WriteLine("Yes");
        else
            Console.WriteLine("No");
    }
}
  
// This code is contributed by Rajput-Ji


输出:

Yes
No

应用范围:

  • Kruskal的最小生成树算法。
  • 作业排序问题。
  • 循环检测

相关文章:
联合查找算法|集合1(无向图中的检测周期)
联合查找算法|第2组(按等级和路径压缩合并)

尝试解决此问题并检查您学到了多少,并对给定问题的复杂性发表评论。