我需要一个数据结构,将0
到N
之间的整数划分为不相交的集合,如下所示:
{0, 1, 7},
{9},
{4, 5},
{6, 2, 8, 3}
有效实施以下操作(优于O(N)
):
元素属于哪个集合?
0 -> [0], 8 -> [3]
迭代集合
的所有成员[2] -> {4, 5}
将元素移动到其他集
8 [3] -> [1]
合并两套
merge [0], [1]
将1个或多个元素移动到新集
[4] = new set {2, 4}
一个明显的解决方案是:
std::vector<std::vector<int>> sets;
std::vector<int> elementToSetIndex;
但是这需要为vector<vector<int>>
进行多次堆分配。如果可能的话,我想使用更少的堆分配。
元素的数量N
将在结构的生命周期内得到修复。
答案 0 :(得分:1)
我正在使用一个解决此类问题的算法类。它使用他们称为Union-Find的数据结构来解决:
推荐阅读:https://www.cs.princeton.edu/~rs/AlgsDS07/01UnionFind.pdf 完成了UF ADT的java实现
答案 1 :(得分:0)
如果对于合并操作,您保证合并的一部分上的值都低于合并另一侧的值,您可以对log(N)执行所有操作,除了列出元素(这是log( N)+ the_number_of_elements)。例如:
将{2,5,7}与{9,10,11}合并=&gt; {2,5,7,9,10,11} 但不是:{2,5,7} {4,6}。
如果这是您想要的,您可以使用Treap:http://en.wikipedia.org/wiki/Treap
稍后编辑:您可以为常规合并案例实施treap解决方案 假设您对Treaps有一些了解,我会尝试解释解决方案
所以这里是如何运作的:
每个元素应代表一个Treap节点。它应该包含指向他父亲和两个儿子的指针,值本身,随机生成的优先级和子树中的节点数。
节点的密钥不会是值(正如您所期望的那样),而是树的顺序遍历中的节点索引(可以在O(log n)中计算,因为我们的结构)
操作1:要找到保存信息的treap,只需要进入treap,直到到达一个不代表值的节点,但是整个treap(你可以有一个布尔字段,或者你喜欢的任何其他方式)。 O(log n)
操作2:只需对该特定treap进行异步遍历,即可完成(它是O(元素数量))
操作3:删除节点(与Treap中的通常一样)并在最后插入特定的Treap(其键将大于该Treap中任何其他元素的键,因此它的键入简单)。两个操作都采用O(log n)
操作4:这个实际上非常简单。您将两个treaps(使用典型的合并treap算法)与右边的任何元素比左边的每个元素都要大的约定合并(因为在右边的每个元素之后,inorder遍历中的每个元素都有一个更高的索引。左边的任何元素)。您可以选择谁离开了谁以及谁是对的,这并不重要。复杂性:O(log N)。
操作5:将一些元素移动到新集合。这相当于
答案 2 :(得分:0)
有一种方法可以进行合并操作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)替换链接到被忽略的根节点,以避免删除它然后再添加它。
迭代集合中的所有成员:通过以下子节点递归迭代所有整数。由于这会迭代所有整数,因此可以删除树集中的所有边(无论类型),并且可以使每个整数与根节点具有直接的节点 - 整数连接。
< / LI>将元素移动到其他集合:在图表中查找元素。删除它的当前节点 - 整数边缘,并将其替换为直接节点 - 整数边缘,并将其替换为新的树集。
合并两个集合:每个根节点都应该跟踪树集的深度。这用于确定要合并的树集的哪个根节点将成为合并树集的根节点。合并k个树集期间的想法是使其他树集的所有根节点直接具有最大深度的树集的根节点的子节点。如果存在两个或更多个最大深度树集,则这导致新深度与最大深度或最大深度相同加一。因此,合并相同深度引线的树集会导致必须将每个操作成本乘以O(log M)。好处是日志实际上至少为2而不是2,因为树集不是严格的二进制。
将一个或多个元素移动到一个新集合:创建一个新的根音符然后将其用作不同的集合并运行&#34;将元素移动到另一个集合&#34;,对于每个元素都是移动。摊销O(k)其中k是要移动的元素数量。
通过允许多个节点 - 超级节点链接并在每个超级节点链的末尾分别对树根进行树形化,可以支持复制树集。这种方法允许延迟复制,其中复制是在一段时间内而不是立即进行的。但是为了简单起见,每个节点只使用一个超级节点会更容易。
编辑:我以为我曾经回忆起之前看过这种做法。这是UnionFind,路径压缩实现为无向图。