寻找具有O(1)索引和O(log(n))插入和删除的数据容器

时间:2012-05-07 07:25:40

标签: algorithm data-structures containers

我不确定它是否可能,但对我来说似乎有点合理,我正在寻找一种允许我进行这些操作的数据结构:

  • 插入带有O(log n)
  • 的项目
  • 删除带有O(log n)
  • 的项目
  • 查找/编辑O(1)中的第k个最小元素,用于任意k(O(1)索引)

当然,编辑不会导致元素顺序的任何变化。什么使它成为可能的是我将按递增的顺序逐个插入元素。因此,例如,如果我尝试第五次插入,我确定在此之前的所有四个元素都比它小,并且在此之后的所有元素将会更大。

6 个答案:

答案 0 :(得分:3)

我不知道这样的数据容器是否可以满足所要求的时间复杂性。但这里有几种方法,几乎​​可以实现这些复杂性。

第一个是tiered vector,其中O(1)插入和索引,但O(sqrt N)删除。由于您希望此容器中只有大约10000个元素,并且sqrt(10000)/ log(10000)= 7,因此您可以获得几乎所需的性能。分层向量是作为一个环形缓冲区数组实现的,因此删除一个元素需要移动所有元素,在环形缓冲区中跟随它,并将一个元素从以下每个环形缓冲区移动到它之前的那个;此容器中的索引意味着在环形缓冲区数组中进行索引,然后在环形缓冲区内进行索引。

可以创建一个与分层向量非常相似的不同容器,具有完全相同的复杂性,但工作速度更快,因为它更易于缓存。分配N元素数组以存储值。并分配一个sqrt(N)元素数组来存储索引更正(用零初始化)。我将展示它如何在100元素容器的示例上工作。要删除索引为56的元素,将元素57..60移动到位置56..59,然后在索引更正数组中将1添加到元素6..9。要查找第84个元素,请在索引更正数组中查找第8个元素(其值为1),然后将其值添加到索引(84 + 1 = 85),然后从主数组中获取第85个元素。在删除主阵列中大约一半的元素之后,必须压缩整个容器以获得连续的存储。这仅获得O(1)累积复杂度。对于实时应用程序,可以通过几个较小的步骤执行此操作。

这种方法可以扩展到深度M的Trie,花费O(M)时间进行索引,O(M * N 1 / M )时间用于删除和O( 1)插入时间。只需分配一个N元素数组来存储值,N (M-1)/ M ,N (M-2)/ M ,...,N < sup> 1 / M - 用于存储索引更正的元素数组。要删除元素2345,在主数组中移动4个元素,在最大的“更正”数组中增加5个元素,在下一个元素中增加6个元素,在最后一个元素中增加7个元素。要从此容器中获取元素5678,请在元素5,56,567中添加5678所有更正,并使用结果索引主数组。为“M”选择不同的值,可以平衡索引和删除操作之间的复杂性。例如,对于N = 65000,您可以选择M = 4;所以索引只需要4次内存访问和删除更新4 * 16 = 64个内存位置。

答案 1 :(得分:1)

我想首先指出,如果 k 实际上是一个随机数,那么可能值得考虑问题可能完全不同:要求第k个最小元素,用k在可用元素的范围内随机均匀地基本上......随机选取元素。而且可以采用不同的方式。

在这里,我假设你实际上需要选择一些特定的,如果是任意的, k


鉴于你的强大前提条件是按顺序插入元素,有一个简单的解决方案:

  • 由于您的元素按顺序给出,只需将它们逐个添加到数组中;那就是你有一些(无限的)表 T 和一个游标 c ,最初 c:= 1 ,当添加一个元素时,做< em> T [c]:= x 和 c:= c + 1
  • 当你想要访问第k个最小元素时,只需看看T [k]。

问题当然是,当你删除元素时,你会在表格中创建空白,这样元素 T [k] 可能不是k-因为 j <= k ,所以j最小,但是k之前的一些单元格是空的。

这足以跟踪您已删除的元素,知道 已被删除的小于 k 。你如何及时达到O(log n)?通过使用range tree或类似类型的数据结构。范围树是一种结构,可让您添加整数,然后查询,用于 X Y 之间的所有整数。因此,无论何时删除项目,只需将其添加到范围树中;当您查找第k个最小元素时,查询已删除的 0 k 之间的所有整数;说 delta 已被删除,那么第k个元素将在T [k + delta]中。

有两个轻微的捕获,需要一些修复:

  • 范围树返回时间范围O(log n),但要计算范围内元素的数量,您必须遍历范围中的每个元素,这样就会增加时间O(D)其中D是范围内已删除项目的数量;要摆脱这种情况,您必须修改范围树结构,以便在每个节点上跟踪子树中不同元素的数量。保持这个计数只会花费O(log n),这不会影响整体复杂性,而且这是一个相当简单的修改。

  • 事实上,只做一个查询是行不通的。实际上,如果在范围1到k中获得delta删除元素,则需要确保在范围k + 1到k + delta中没有删除元素,依此类推。完整的算法将是下面的内容。


KthSmallest(T,k) := {
  a = 1;  b = k;  delta

  do {
    delta = deletedInRange(a, b)
    a = b + 1
    b = b + delta
  while( delta > 0 )

  return T[b]
}

此操作的确切复杂程度取决于您删除的确切程度,但如果您的元素随机均匀删除,则迭代次数应该相当小。

答案 2 :(得分:0)

查看heaps。插入和删除应为O(log n),并且最小元素的偷看是O(1)。但是,偷看或检索第K个元素将再次为O(log n)。

EDITED:正如amit所说,检索比偷看更贵。

答案 3 :(得分:0)

这可能是不可能的。 但是,您可以在平衡二叉树中进行某些更改,以便在O(log n)中获取第k个元素。

在此处详细了解:Wikipedia.

答案 4 :(得分:0)

可索引的跳过列表可能能够(关闭)您想要的内容: http://en.wikipedia.org/wiki/Skip_lists#Indexable_skiplist

然而,有一些警告:

  1. 这是一个概率数据结构。这意味着它不一定是所有操作的O(log N)
  2. 索引不会是O(1),只是O(log N)
  3. 根据您的RNG的速度以及根据指针移动的速度,您可能会因此而不仅仅是坚持使用阵列并处理更高的删除成本。
  4. 最有可能的是,为实现目标,你可以做的事情就是“最好的”。

答案 5 :(得分:0)

有一个Treelist(Java实现,包含源代码),对于所有三个操作都是O(lg n)(插入,删除,索引)。

实际上,此数据结构的可接受名称似乎是“order statistic tree”。 (除了索引之外,它还被定义为支持O(lg n)中的 indexof(元素)。)

顺便说一句,O(1)并不比O(lg n)更有优势。这种差异往往被实践中的常数因素所压倒。 (你在数据结构中是否会有1e18个项目?如果我们将其设置为上限,那就等于60左右的常数因子。)