用于在查询范围内有效查找整数的数据结构

时间:2011-12-08 15:42:56

标签: algorithm optimization data-structures

已知范围中有任意数量的不同无符号整数值。

整数值的数量是<<范围内的整数。

我想构建一个允许以下运行时复杂性的数据结构:

  1. 插入O(1)
  2. 插入完成后:
    • 删除O(1)
    • 获取O(k)中查询范围内的所有值,其中k是结果值的数量(不必对返回的值进行排序)
  3. 内存复杂性不受限制。但是,天文数量太大的内存不可用; - )

    以下是一个例子:

    • range = [0,1023]
    • insert 42
    • insert 350
    • insert 729
    • insert 64
    • 插入1
    • 插入680
    • insert 258
    • 在[300; 800]中找到值;返回{350,729,680}
    • 删除350
    • 删除680
    • 在[35; 1000]中找到值;返回{42,258,64,729,258}
    • 删除42
    • 删除258
    • 在[0;中找到值5];返回{1}
    • 删除1

    这样的数据结构是否可行? (借助查询表等)?

    近似我想到的是:

    • 将插入的值插入存储桶。 0..31 => bucket 0,32..63 => bucket 1,64..95 =>桶2,96..127 =>斗3,......

    • 插入:使用简单的移位算法查找存储桶ID,然后将其插入每个存储桶的数组中

    • 查找:使用移位算法查找start和endpoint的bucket id。查看第一个和最后一个存储桶中的所有值,并检查它们是否在范围内或范围之外。将所有中间桶中的所有值添加到搜索结果

    • 删除:使用shift查找存储桶ID。交换要删除的值,使用存储桶中的最后一个值,然后减少此存储桶的计数。

    下行:如果有许多查询查询范围小于32的值,则每次都会搜索整个存储桶。

    下行2:如果范围内有空桶,则在搜索阶段也会访问它们。

2 个答案:

答案 0 :(得分:7)

从理论上讲,van Emde Boas tree是最好的选择,使用O(log log M)时间操作,其中M是范围的大小。空间使用量非常大,但有更高效的变体。

实际上,Mortensen,Pagh和Patrascu在论文On Range Reporting in One Dimension中描述了理论上的现有技术。

我不确定现有的下限是否排除O(1),但是M不足以使区分成为问题。而不是vEB结构,我只使用k-ary trie,k为2或2的幂,如32或64.

编辑:这是使用特里进行范围搜索的一种方法。

让我们假设每个数据都是一个位模式(很容易;这就是CPU如何看待它)。每个子树由具有特定前缀的所有节点组成。例如,{0000,0011,0101,1001}由以下4-ary trie表示,其中X表示空指针。

+---+---+---+---+
|00\|01\|10\|11X|
+--|+--|+--|+---+
   |   |   |
   |   |   +----------------------------+
+--+   |                                |
|      +------------+                   |
|                   |                   |
v                   v                   v
+---+---+---+---+   +---+---+---+---+   +---+---+---+---+
|00\|01X|10X|11\|   |00X|01\|10X|11X|   |00X|01\|10X|11X|
+--|+---+---+--|+   +---+--|+---+---+   +---+--|+---+---+
   |           |           |                   |
   v           v           v                   v
  0000        0011        0101                1001

一些优化很快就会变得明显。首先,如果所有的位模式都是相同的长度,那么我们不需要将它们存储在叶子上 - 它们可以从下降路径重建。我们所需要的只是位图,如果k是机器字中的位数,那么就可以很好地适应前一级指针的位置。

+--------+--------+--------+--------+
|00(1001)|01(0100)|10(0100)|11(0000)|
+--------+--------+--------+--------+

为了在trie中搜索类似[0001,1000]的范围,我们从根开始,确定哪些子树可能与范围相交并递归它们。在此示例中,根的相关子项为00,01和10. 00的相关子项是表示前缀0001,0010和0011的子树。

对于k固定,来自k-ary trie的报告是O(log M + s),其中M是位模式的数量,s是命中数。不要被愚弄 - 当k为中等时,每个节点占用一对缓存行,但trie不是很高,因此缓存未命中的数量非常小。

答案 1 :(得分:0)

如果查询操作要求告知至少一个已在相关范围内的现有成员的值,则可以实现目标(O(1),O(1)和O(k))(或许下限。您能否保证至少知道该系列的一名成员?我猜不会。如果可以,我会扩大。

我现在将重点放在指定的问题上。数据结构中的每个数字应构成链表的一部分,这样每个数字都知道数据结构中的下一个最大数字。在C ++中

struct Number {
    struct Number *next_highest;
    int value;
};

显然,该集合中的最大值将为next_highest==NULL,但除此之外this->value < this->next_highest->value

要添加,删除或查询,我们需要能够找到与特定查找值接近的现有Number

set<Number *, specialized_comparator_to_compare_on_value_t >

插入和删除将是O(log(N)),并且查询将是O(log(N)+ k)。 N是当前集合中的值的数量,正如您所说的,它将远小于M(相关数据类型的可能值的数量)。因此log(N)<日志(M)。但在实践中,还应考虑其他方法,例如尝试和此类数据结构。