当前位置: 首页 > article >正文

不相交的集合数据结构

什么是不相交集数据结构?

如果两个集合没有任何共同元素,则称为不相交集合,集合的交集是空集。

存储不重叠或不相交的元素子集的数据结构称为不相交集数据结构。不相交集数据结构支持以下操作:

  • 将新集添加到不相交集。
  • 使用Union操作将不相交的集合合并为单个不相交的集合。
  • 使用查找操作查找不相交集的代表。
  • 检查两组是否不相交。 

考虑有很多人的情况以及要对他们执行的以下任务:

  • 添加一个新的友谊 关系,即一个人 x 成为另一个人 y 的朋友,即向集合中添加新元素。
  • 查找个人x 是否是个人 y 的朋友(直接或间接朋友)

例子: 

我们有 10 个个体 定义为, a, b, c, d, e, f, g, h, i, j

以下是要添加的关系:a <-> b  
b <-> d
c <-> f
c <-> i
j <-> e
g <-> j

给出诸如 a 是否是 d 的朋友之类的查询。我们基本上需要创建以下 4 个组并在组项之间保持快速访问连接:G1 = {a, b, d}
G2 = {c, f, i}
G3 = {e, g, j}
G4 = {h}

判断x 和y 是否属于同一组,即判断x 和y 是否是直接/间接朋友。

根据个人所属的群体将他们分成不同的集合。这种方法被称为不相交集联合,它维护一个不相交集的集合,每个集合由它的一个成员表示。

要回答上述问题,需要考虑两个关键点:

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

使用的数据结构是: 

大批:

整数数组称为Parent[]。如果我们正在处理N 个项目,则数组的第 i 个元素代表第 i 个项目。更准确地说,Parent[] 数组的第 i 个元素是第 i 个项的父项。这些关系创建了一棵或多棵虚拟树。

树:

它是一个不相交的集合。如果两个元素在同一棵树中,则它们在同一个Disjoint set中。每棵树的根节点(或最顶层节点)称为集合的代表。每组总是有一个唯一的代表。识别代表的一个简单规则是,如果“i”是集合的代表,则Parent[i] = i。如果 i 不是他集合的代表,则可以通过沿着树向上移动找到它,直到找到代表。

操作:

寻找:

可以通过递归遍历父数组直到我们找到一个本身是父节点的节点来实现。

// Finds the representative of the set
// that i is an element of

#include<bits/stdc++.h>
using namespace std;

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]);
	}
}

// The code is contributed by Nidhi goel

联合 

它以两个元素作为输入,并使用Find操作找到它们集合的代表,最后将其中一棵树(代表集合)放在另一棵树的根节点下,有效地合并树和集合。

  • C++
// Unites the set that includes i
// and the set that includes j

#include <bits/stdc++.h>
using namespace std;

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;
}

改进(按等级联合和路径压缩):

效率在很大程度上取决于树的高度。我们需要最小化树的高度以提高效率。我们可以使用Path Compression 和 Union by rank方法来做到这一点。

路径压缩(对 Find() 的修改):

它通过压缩树的高度来加速数据结构。可以通过在查找操作中插入一个小的缓存机制来实现。查看代码以获取更多详细信息:

  • C++
// Finds the representative of the set that i
// is an element of.

#include <bits/stdc++.h>
using namespace std;

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[]的新整数数组。该数组的大小与父数组Parent[]相同。如果i是一个集合的代表,rank[i]就是代表这个集合的树的高度。 
现在回想一下,在 Union 操作中,将两棵树中的哪棵移到另一棵树下并不重要(请参见上面最后两个图像示例)。现在我们要做的是最小化结果树的高度。如果我们联合两棵树(或集合),我们称它们为左和右,那么这完全取决于left 的等级right 的等级。 

  • 如果left的等级低于right的等级,那么最好将left 移动到 right 下方,因为这不会改变 right 的等级(而将 right 移动到 left 下方会增加高度)。同理,如果右边的阶数小于左边的阶数,那么我们应该从右移到左下方。
  • 如果等级相等,则哪棵树在另一棵树下并不重要,但结果的等级总是比树的等级大 1。
     
  • C++

// Unites the set that includes i and the set
// that includes j

#include <bits/stdc++.h>
using namespace std;

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++ implementation of disjoint set

#include <bits/stdc++.h>
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;
		}
	}
};

// Driver Code
int main()
{

	// Function Call
	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;
}
输出
Yes
No

创建 n 个单项集的时间复杂度为 O(n ) find() 和 Union() 操作的时间复杂度是 O(log n)。因此,不相交集数据结构的整体时间复杂度为 O(n + log n)。

空间复杂度为 O(n),因为我们需要在不相交集合数据结构中存储 n 个元素。


http://www.kler.cn/a/5927.html

相关文章:

  • vs2022开发.net窗体应用开发环境安装配置以及程序发布详细教程
  • 基于SpringBoot的洗浴管理系统
  • Ubuntu 20.04安装gcc
  • 加速科技荣获“浙江省企业研究院”认定
  • 【2024华为OD-E卷-100分-boss的收入】(题目+思路+JavaC++Python解析)
  • [ LeetCode 75 ] 1768. 交替合并字符串
  • PerfEnforce Demonstration: Data Analytics with Performance Guarantees
  • 涨点技巧:Yolov5/Yolov7 引入Yolo-Z---ResneXtBottleneckCSP和DenseBlock,提升小目标检测能力
  • PCB模块化设计13——FLASH、DDR和eMMC高速PCB布局布线设计规范
  • QT学习(四)——常用控件
  • 阿里P8高级技术专家自述被裁员,疑似给市长写信,房贷月供3w,压力很大,出门面试找工作很难!...
  • 谷粒商城笔记+踩坑(22)——库存自动解锁。RabbitMQ延迟队列
  • python---数据容器
  • DRF知识点总结
  • 来给大家解释一下赌博为什么会输成穷光蛋。
  • 【C4D】基础快捷键操作,布尔操作——动不了怎么办+选不上怎么办+怎么移动+怎么拉平面或拉平一圈线
  • 算法第二十期——FLoyd算法的入门与应用
  • 我给Chat GPT写了个记忆系统
  • windows 电脑图片/视频不展示预览图
  • 线段树:解决区间查询和区间修改的利器
  • Activity登堂入室
  • 树状数组 基础知识——C++数据结构
  • STM32学习(十三)
  • 讲解有哪些实用的数据恢复工具
  • 【C语言】整形数据的存储和读取过程
  • 【算法】【数组与矩阵模块】矩阵中的最短通路值