考虑一个有很多人的情况,并要对他们执行以下任务。
- 添加一个新的友谊关系,即一个人x成为另一个人y的朋友。
- 查找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组(按等级和路径压缩合并)
尝试解决此问题并检查您学到了多少,并对给定问题的复杂性发表评论。