带有O(logN)插入的排序数据结构,提供插入点索引

时间:2015-03-03 01:08:07

标签: java arrays data-structures

我的目标是一个可以完成两件事的排序数据结构:

  1. 快速插入(根据排序顺序在该位置)
  2. 我可以快速将数据分段为大于或小于或等于元素的所有内容。 我需要知道每个分区的大小,我需要能够“获取”这些分区。
  3. 目前,我正在使用ArrayList在java中实现这一点,它很容易提供#2,因为我可以执行二进制搜索(Collections.binarySearch)并获得一个插入索引,告诉我一个元素在什么时候点会被插入。然后根据索引范围从0到数组大小的事实,我立即知道有多少元素大于我的元素或小于我的元素,我可以很容易地得到这些元素(作为子列表)。但是,这没有属性#1,导致阵列复制过多。

    这使得我想使用SkipList或RedBlackTree这样可以更快地执行插入的东西,但是我无法弄清楚如何在不花费O(N)时间的情况下满足属性#2。

    任何建议将不胜感激。谢谢

    编辑:感谢下面的答案,参考数据结构在O(logN)时间执行插入,并且可以快速分区,但我想突出显示size()要求 - 我需要知道的大小这些分区无需遍历整个分区(according to this就是TreeSet所做的。这背后的原因是在我的用例中,我使用不同的数据结构保存数据,每个数据结构使用不同的比较器,然后需要问“根据什么比较器是比最小的特定元素大的所有东西的集合”。在ArrayList情况下,这实际上很简单,只需要O(YlogN),其中Y是数字比较器,因为我只是二元搜索每个Y数组并返回具有最高插入索引的arraylist。我不清楚如何使用TreeSet而不使用O(YN)。

    我还应该补充一点,插入索引的近似答案仍然很有价值,即使它无法准确解决。

4 个答案:

答案 0 :(得分:4)

使用共同的Java TreeSet。插入需要O(logN),因此完成了您的要求的第1项。这是文档中的qouting:

  

此实现为基本操作(添加,删除和包含)提供了有保证的log(n)时间成本。

当它实现NavigableSet interface时,您可以使用以下方法获得#2或您的要求:

这些操作超载了包含/排除提供的边界的版本。

TreeSet非常灵活:它允许您定义Comparator以自定义方式订购Set,或者您也可以依赖自然排序 of elements。

修改

根据返回子集size()的操作要求为O(n),我担心Java API中没有特殊实现。

确实,TreeSet范围操作返回的设置视图,通过“跳跃”实现size()。在O(log n)时间内查看视图的第一个元素,然后迭代后续元素,在每次迭代中添加1,直到到达子集的末尾。

我必须说这是非常不幸的,因为并不总是需要遍历返回的子集视图,但有时候,事先知道子集的大小可能非常有用(因为它是你的用例)。

因此,为了满足您的要求,您需要另一种结构,或者至少需要一种辅助结构。经过一些研究,我建议你使用Fenwick tree。 Fenwick树也称为二进制索引树(BIT),可以是不可变的也可以是可变的。不可变版本用数组实现,而可变版本可以用平衡二叉树实现,即红黑树(Java TreeSet实际上实现为红黑树)。 Fenwick树主要用于存储频率,并计算O(log n)时间内给定元素的所有频率之和。

请参阅this question here on Stack Overflow以获得对这个非常未知但非常有用的结构的完整介绍。 (正如Stack Overflow中的解释一样,我不会在这里复制它。)

Here's another Stack Overflow question询问如何正确初始化Fenwick树,here's actual Java code showing how to implement Fenwick tree's operations。最后,here's a very good theoretic explanation关于结构和使用的基础算法。

Web中所有示例的问题在于它们使用不适合您的结构的不可变版本,因为您需要将查询与添加元素交织到结构中。但是,它们对于完全理解所使用的结构和算法非常有用。

我的建议是你研究Java TreeMap的实现,看看如何修改/扩展它,以便你可以把它变成一个 Fenwick树来保持{{} 1}}作为每个键的值。这个1将是每个键的频率。因此,Fenwick树的基本操作1实际上会在getSum(someElement)时间内将子集的大小从第一个元素返回到someElement

所以挑战是实现一个平衡的树(实际上是Java的红黑O(log n)的后代),它实现了所有Fenwick树的操作你需要。我相信您已完成TreeMap,但也许您还可以扩展返回的子树范围视图,以便在为范围视图实施getSum(somElement)操作时它们都引用getSum(someElelment)

希望这有帮助,至少我希望它是一个好的起点。如果您需要澄清以及示例,请告诉我。

答案 1 :(得分:2)

如果您不需要重复元素(或者如果您可以使元素看起来不同),我会使用java.util.TreeSet。它符合您的要求。

  1. 由于二叉树结构而导致的O(log n)排序插入
  2. 使用in-place subsets
  3. 的O(log n)细分时间

    不幸的是,由于answer you linked中的原因,您需要总是知道段的大小,因此O(log n)分段时间有效地减慢到O(n)。就地子集在你问它们之前不知道它们的大小,然后它们就算了。存储计数的大小,但如果以任何方式更改了支持集,则子集必须再次计数。

答案 2 :(得分:1)

我认为此问题的最佳数据结构是B-Treedense index。这样的B树是由以下内容构建的: - 内部节点仅包含指向子节点的指针 - 包含指向分页数组的指针的叶子 - 许多大小相等的数组(页面)

不幸的是,Java中的B-Tree通用实现很少,可能是因为存在很多变化。

插入费用为

  • O(log(n))找到位置
  • O(p)将新元素插入页面(其中p是常量页面大小)

也许这种数据结构也涵盖了您的细分问题。如果不是:提取的成本将是

  • O(log(n))找到边框
  • O(e)复制提取物(其中e是提取物的大小)

答案 3 :(得分:1)

获得所需内容的一种简单方法是在每个节点添加左右子树大小来增加您喜欢的二叉搜索树数据结构(红黑树,AVL树等等) - 称之为 L尺寸 R尺寸

假设可以有效地更新树数据结构中的这些字段(比如恒定时间)。那么这就是你得到的:

  • 插入,删除和所有常规二进制搜索树操作与您选择的数据结构一样高效---红背树的O(log n)。
  • 给定一个密钥x,你可以通过在树中下降找到x的适当位置来得到树中元素的数量,这些元素在O(log n)时间内的密钥小于x,总结了L-每次“正确”时,大小(加上您正在遍历的实际节点的大小)#34;。 "大于"案例是对称的。
  • 给定一个关键x,你可以在O(log n + | x_L |)时间内得到小于x的元素的排序列表x_L,再次从树下降到找到x的适当位置,并且每次向右移动时,标记刚刚遍历的节点,将其附加到列表h_L。然后对h_L中的每个节点进行有序遍历(按h_L的顺序)将为您提供x_L(已排序)。 "大于"案例是对称的。

最后,对于我的工作答案,我需要向您保证,我们可以有效地维护这些L和R大小,以便您选择特定的树数据结构。我会考虑红黑树的情况。

请注意,对于vanilla二叉搜索树,保持L大小和R大小是在恒定时间内完成的(当您从根开始添加节点时,如果节点应该在左子树中,则只需将一个添加到L大小,如果它进入正确的子树,则为一个R-大小)。现在,红黑树的额外平衡过程只会通过节点的局部旋转来改变树结构 - 见Wikipedia's depiction of rotations in red-black trees。很容易看出,可以从A,B,C的L尺寸和R尺寸重新计算P和Q的旋转后L尺寸和R尺寸。这只会给红黑树的操作增加一定量的工作。