Data structures 用于将0到N的整数划分为不相交集的数据结构

Data structures 用于将0到N的整数划分为不相交集的数据结构,data-structures,Data Structures,我需要一个数据结构,将0到N的整数划分为不相交的集合,如下所示: {0, 1, 7}, {9}, {4, 5}, {6, 2, 8, 3} 有效执行以下操作(优于O(N)): 元素属于哪个集合 迭代集合的所有成员 将元素移动到其他集合 合并两套 将一个或多个元素移动到新集合 一个显而易见的解决方案是: std::vector<std::vector<int>> sets; std::vector<int> elementToSetIndex; std::向

我需要一个数据结构,将
0
N
的整数划分为不相交的集合,如下所示:

{0, 1, 7},
{9},
{4, 5},
{6, 2, 8, 3}
有效执行以下操作(优于
O(N)
):

元素属于哪个集合

迭代集合的所有成员

将元素移动到其他集合

合并两套

将一个或多个元素移动到新集合

一个显而易见的解决方案是:

std::vector<std::vector<int>> sets;
std::vector<int> elementToSetIndex;
std::向量集;
std::向量元素到设置索引;
但这需要为
向量
分配许多堆。如果可能的话,我希望使用更少的堆分配


在结构的生命周期内,元素的数量将是固定的。

如果对于合并操作,您保证合并的一部分上的值都低于合并的另一部分上的值,您可以在日志(N)上执行所有操作,除了列出元素(即log(N)+元素的\u编号)。例如:

合并{2,5,7}与{9,10,11}=>{2,5,7,9,10,11} 但不是:{2,5,7}和{4,6}

如果这是您想要的,您可以使用Treap:

稍后编辑:您可以为一般合并案例实现treap解决方案 假设您对Treaps有一些常识,我会尝试解释解决方案

下面是它的工作原理:

  • 每个元素都应该表示一个Treap节点。它应该包含一个指向其父和两个儿子的指针、值本身、随机生成的优先级以及子树中的节点数

  • 节点的键不是值(正如您所期望的那样),而是在树的顺序遍历中节点的索引(由于我们的结构,可以在O(logn)中计算)

  • 操作1:要找到保存信息的treap,只需在treap上向上移动,直到到达一个节点,该节点不表示值,而是表示整个treap(可以有一个布尔字段或任何其他方式)。O(对数n)

  • 操作2:只需按顺序遍历那个特定的treap,就完成了(它是O(元素数))

  • 操作3:移除节点(与Treap中的常见操作一样),并在末尾插入特定的Treap(其密钥将大于该Treap中任何其他元素的密钥,因此很容易)。两个操作都需要O(日志n)

  • 操作4:这个其实很简单。您将两个treap(使用典型的merge-treap算法)合并为一种约定,即右侧的任何元素的比较都大于左侧的每个元素(因为在合并后,右侧的每个元素在按序遍历中的索引都高于左侧的任何元素)。你可以选择谁左谁右,没关系。复杂性:O(logn)

    • 我猜想这里的合并意味着将所有的东西从一个集合移动到另一个集合,让第一个集合为空
  • 操作5:将一些元素移动到新集合。这相当于

    • 创建一个新集合:只需创建一个新的treap根O(1)
    • 移动每个元素,每个元素执行操作3:O(logn)

有一种方法可以使合并操作O(1)并为大多数其他操作提供摊销O(1)运行时间。尽管如此,重复的合并操作可能会将速度降低到O(M),其中M是已完成的合并数。这是通过一个树的惰性计算来完成的,该树不限制每个节点有多少子节点

最好的方法是使用无向林图。每个集合都变成一棵树,为了清晰起见,我将其称为树集合。与树集的大多数实现不同,此树不是二叉树。树集中的每个节点都包含三种类型的链接。如果节点不是树集的根,则存在单节点超级节点链接。还有任意数量的节点子节点(子节点)链接以及任意数量的节点整数链接。应该这样做,以便每次删除都可以以O(1)的成本添加/删除整数/子节点/超级节点。超级节点用于允许向根进行遍历,而不仅仅是远离根

操作如下所示,示例中将节点标记为字母

  • 一个元素属于哪个集合?:在图中查找该元素,然后沿着树集合向上移动,直到到达根节点,并跟踪沿途访问的节点。剪切节点之间的链接,使元素和每个节点直接链接到超级节点。例如,如果所遵循的路径是[1,A,B,C,Z],Z是树集的根节点。然后,无向边(1,A)、(A,B)和(B,C)将被删除,并替换为边(1,Z)、(A,Z)和(B,Z),忽略到根节点的直接链接,以避免删除它,然后再将其添加回去

  • 迭代集合的所有成员:通过以下子节点递归迭代所有整数。由于这将迭代所有整数,因此可以删除树集中的所有边(无论类型如何),并且可以使每个整数与根节点具有直接的节点整数连接

  • 将一个元素移动到另一个集合:在图形中查找该元素。删除它的当前节点整数边,并将其替换为具有其新树集根的直接节点整数边

  • 合并两个集合:每个根节点都应该跟踪树集合的深度。这用于确定要合并的树集的哪个根节点将成为合并树集的根节点。合并k个树集时的想法是使其他树集的所有根节点直接指向深度最大的树集根节点的子节点。这将导致新深度与“最大深度”相同,或者如果有两个或多个“最大深度”树集,则“最大深度”加上一
    [2] -> {4, 5}
    
    8 [3] -> [1]
    
    merge [0], [1]
    
    [4] = new set {2, 4}
    
    std::vector<std::vector<int>> sets;
    std::vector<int> elementToSetIndex;