一组非常小的整数的高效数据结构

时间:2018-01-29 05:27:05

标签: algorithm data-structures set

我想要一个有效的数据结构,它代表0..k范围内的一组整数,其中k非常小,让我们说小于1024

我提出了一个具有以下复杂性的实现:

initialize: O(1)
size: O(1)
contains: O(1)
insert: O(1)
remove: O(1)
clear: O(1)
union: O(k)

但是,我怀疑O(1)可能真的是O(log(k)),因为我们必须在某处读取O(log(k))位数。

内存要求为O(k)

我没有实施equalsintersectiondifference因为我不需要它们,但它们都是O(k)。我的实现确实支持迭代。

这似乎可能出现在许多不同的问题中,所以我想知道我的实现是否已经完成了?

更重要的是:我们能做得更好吗?

显然更好有点主观,但到处都只有O(1)O(log(k))的东西可能会很有趣。

我怀疑bitsetsk稍大一点时可能更实用,但我不确定这么小的k

这是我所拥有的伪代码。它的工作原理是分配长度为kk+1的数组和地图(实现为另一个数组),但只​​关心数组的第一个m元素,其中{{1} }是集合的当前大小。

m

编辑:

根据我的理解,我想像一个bitset实现看起来像:

    initialize: O(k)
    size: O(popcount of k bits) O(1)
    contains: O(1)
    insert: O(1)
    remove: O(1)
    clear: O(k)
    union: O(bitwise OR of k bits)

如果我们使用1024位的bitset,我不确定这是否更好。

2 个答案:

答案 0 :(得分:1)

渐近地,您的数据结构相当整洁(大小为O(K),因为您需要大约2 * k整数来存储所有内容。)

我认为常量可能有点高,内存使用率还不够理想。

内存方面,你是对的,1024位(128字节)的位组也可以正常工作。将其与您的设置2 * 1024 * 4 = 8Kb进行比较。

您担心初始化。无论如何你需要分配内存,你很有可能找到128字节已经可用(与8K相比)。您需要初始化它们,但是在可能是一个或两个SIMD指令的现代架构上(编译器会在启用优化时为您执行此操作,并指定支持它们的目标平台)。

此外,128个字节将适合2个64字节缓存行,因此您的所有数据都可能适合L1缓存。

包含在代码中被称为很多。在您的建议中,您需要执行arr[map[x]] == x。这是两个链式查找。您有2倍的缓存未命中率(因为数据太大)。此外,它无法轻松优化(CPU需要等待第一次查找的值才能发出第二次查找)。

总而言之,除了内存之外,这两个数据结构非常相似。在实践中,我敢打赌,bit-set会明显加快,特别是如果你在编译器中启用了优化。

在任何情况下,要确定您应编写代​​码的基准并在预期的平台上运行它。然后交换数据结构并选择能够提供最佳结果的数据结构。

答案 1 :(得分:1)

这看起来有点hacky,但您可以将该集存储到64位整数int64[16]或类似的32位整数int32[32]的数组中,前4位确定哪个索引是元素属于,最后6位确定将在整数中设置哪个位。所有操作都是O(1),除了clear和union将是O(log k / 64),其中k是集合中的最大元素

首先,我们有int64[16]set

添加元素x

set[x >> 6] |= 1 << (0x3F & x) // Only consider first 6 bit

删除元素

set[x >> 6] ^= 1 << (0x3F & x)

要清除:

for int i = 0; i < 16; i++{
    set[i] = 0;
} 

结合两组ab

for int i = 0; i < 16; i++{
    set[i] = a[i] | b[i];
}

根据要求,检查集合是否包含x

(set[x >> 6] & (1 << (0x3F & x))) != 0 

为了跟踪集合中元素的数量,一个明显的解决方案是遍历数组中每个元素的每个位,这将是O(k)时间复杂度。

很少有解决方案来计算O(1)中的位数,就像这个How to count the number of set bits in a 32-bit integer?

此外,如果我们改为使用int8[128]设置代替当前解决方案,这意味着我们只使用数字的前3位来确定要设置的位,我们可以使用硬编码数组来保持跟踪它的位数,例如:

int numberOfElement = 0;
int[1<<8]bitCount // Pre-populated value, so bitCount[i] will give an answer of how many bit is set in i

for int i = 0; i < 128; i++ {
    set[i] = a[i] | b[i];
    numberOfElement += bitCount[set[i]];
}