#insert #element #sorting #delete #set #rotated #√n

rotated-array-set

支持O(1)秩和O(√n)插入和删除的有序集合

2个版本

0.1.1 2020年1月9日
0.1.0 2020年1月9日

#1495 in 数据结构

Apache-2.0

68KB
979

rotated-array-set

在Rust中实现的2级旋转数组

此仓库包含了对“2级旋转数组”结构的实现、单元测试和基准代码,该结构首次在Munro和Suwanda于1979年的论文《快速搜索和更新隐式数据结构》中提出(该论文还引入了更广为人知的beap数据结构)。该结构在1983年的论文《字典问题的隐式数据结构》和2001年的论文《简洁动态数据结构》中得到了进一步的发展和讨论。(后者将这一思想推广到了动态数组抽象数据类型,而不是有序数组。)

2级旋转数组相对于普通有序数组的理论优势在于,它提供了相同的搜索性能(O(log n)),具有更好的插入和删除性能(O(√n),与有序数组的O(n)相比),在相同的空间量(即不超过数据本身的空间)。(对于纯隐式结构,插入和删除是O(√n * log n),但可以使用O(√n)额外空间降低到O(√n),这是一个可以忽略的数量。此实现遵循这一方法。)这种插入/删除性能比平衡树(例如红黑树或B树)的O(log N)性能差得多,但2级旋转数组占用的空间仅为平衡树空间的一小部分(小于Rust的BTreeSet空间的一半)。

以下是关于2级数组数据结构的详细描述,来源于"一种表示动态多重集的紧凑数据结构"

我们使用一个两级旋转数组来存储元素,并附加指针指向每个旋转数组的开始。旋转数组是有序数组的任意循环移位。在两级旋转数组中,元素存储在一个分为⌈√(2n)块中的数组中,其中第i块是一个长度为i的旋转数组。第i块中的所有元素都小于或等于第i + 1块中的任何元素。我们显式地存储每个块的起始位置。要搜索元素e,我们首先进行二分搜索以找到两个相邻的块,其中e的第一个(最后一个)出现可以位于其中。然后我们在这些块中进行二分搜索以找到答案。因此,基于元素的搜索可以在O(lg n)的最坏情况下时间复杂度内得到支持。通过记录元素所在的块以及从块开始处的偏移量,可以实现在O(1)的最坏情况下时间复杂度内获得该元素的定位器。给定一个定位器,可以在O(1)的最坏情况下时间复杂度内轻松获得其前驱或后继的定位器。要插入元素e,我们首先找到e应插入的位置(使用上界)。我们通过从旋转数组中删除最后一个元素并将新元素与最后一个元素之间的所有元素向前移动一个位置来在该块中执行插入。然后我们必须按照以下方式更新插入块之后的每个块:从当前旋转数组中删除最后一个元素,并插入来自上一个旋转数组的最后一个元素,该元素现在成为旋转数组的新第一个元素。此外,我们还需要在O(1)的时间复杂度内更新每个块的起始位置。因此,可以在O(√n)的最坏情况下时间复杂度内得到支持。删除操作可以类似执行。

在实践中,这种数据结构受到一般隐式结构(如二叉堆和堆排序)常见的一个问题:它在内存效率方面很高,但在缓存效率方面并不特别高。也就是说,它只使用了缓存未命中或页面故障传输的数据的一小部分,因此它未能实现渐近分析所暗示的效率。尽管如此,它仍然比普通有序数组的插入/删除性能提高了1-3个数量级(尽管它比Rust的BTreeSet慢2-3个数量级),因此在内存效率或索引性能至关重要,但数组的插入/删除性能无法接受的情况下,它可能是一个不错的选择。(注意,可以通过将子树大小信息添加到平衡树中来实现O(log N)的索引[而权重平衡树已经包含此信息],但Rust的标准库中没有这样的数据结构。)

这是一个动态数组的相同数据结构实现(大致上是一个即插即用的替代品,除了它不支持对切片的解引用)可以在 https://github.com/senderista/rotated-vec 找到。

这个实现是用 Rust 编写的,并使用 Criterion 基准测试框架进行基准测试。初步基准测试结果在这里:这里

依赖关系

~72KB