快速排除成员是不是在集合中

Posted

技术标签:

【中文标题】快速排除成员是不是在集合中【英文标题】:fast rule out if a member is in a set快速排除成员是否在集合中 【发布时间】:2014-03-10 14:32:46 【问题描述】:

我有一小组数字,我需要经常搜索。

该组是静态的并且在开始时是已知的。

我从观察中知道,大多数时候我正在搜索的数字不在组中。

我正在寻找的是一种算法,只需一两条指令即可:

    永远不要说某个号码不在组中,而是在组中 该算法大部分或所有时间都预测是否是

例如,

如果数字是 x,y,z 我可以执行以下操作:

保存:

tmp = (x | y | z)

当我搜索一个我可以做的数字时:

if ((num & tmp) == (num))
    do the real search

如果数字是 x、y 或 z,则保证在与它进行 AND 时返回 num。 如果不是,我可能什么也不搜索 - 但基本上没问题。

这个测试的主要问题是大多数时候对于组中超过 5 个数字,即使 num 不在组中,我也会得到 TRUE。

我正在考虑使用 XOR 魔法:

tmp = (x ^ y ^ z)

并且在搜索时执行以下操作:

(num ^ tmp)

但我不明白这如何帮助我确定元素是否在组中。

有什么想法吗?

谢谢,

伊泰

更新

我发现有用的是使用非常简单的布隆过滤器:

我已将 x、y 和 z 散列到一个位数组(例如 8 位)。 然后,我将结果转移到正确的位:

uint8_t digest = (1 << (x % 8)) | (1 << (y % 8)) | (1 << (z % 8))

以及我使用过的搜索功能:

if ( (1 << (num % 8)) & digest )

我使用随机数进行了一些分析,发现使用 8 位在大约 30% 的时间给了我错误的指示。 使用 16 位让它变得更好。

【问题讨论】:

您应该提供一个或多个您需要代表的组的示例,因为这可以帮助我们找到有趣的模式。 你为什么不使用布尔数组查找?如果数字范围太大,可能会使用一些原始散列。 您是否考虑过使用例如qsort 对列表进行排序,然后使用bsearch 进行搜索?您的按位算术是一个很酷的想法,但是随着您的数字列表的增长,将它们组合在一起将提供所有 1 的可能性越来越大,而且我看不到对它们进行异或运算将如何为您提供所需的结果。 我没有对它们进行排序的原因是该组非常小(最多 7 个数字)并且例程对性能非常敏感。这就是为什么我试图找到最快的排除检查。 @rcgldr 分配 528 兆字节的零,整个过程中只设置了 7 位,这是通过缓存抖动降低性能的好方法,并且可能是最慢的方法。 【参考方案1】:

只有七个数字,你应该在你的集合中进行暴力搜索;它会比任何其他方法都快。如果您的值是 16 位或更少,则可以在单个 SIMD 相等测试中进行;如果它们是 32 位,则可以分成两部分。

【讨论】:

对集合进行排序仍然会产生边缘结果,因为一旦找到大于搜索词的数字,您就可以退出搜索。 @DanFarrell 提前退出很可能会因为分支错误预测停顿而降低性能。 @ItayMarom 大多数与高效做事相关的好想法都依赖于平台。 实际上,您的第一个答案(您已删除的那个)帮助我找到了解决方案:一个简单的布隆过滤器。 billmill.org/bloomfilter-tutorial 我实际上认为布隆过滤器在这里不是一个好主意。由于集合中的元素数量很少,评估哈希函数可能比简单的搜索更昂贵。【参考方案2】:

使用一个简单的散列函数将您的域映射到一个恒定范围(比如a.x mod b 或类似的)和一个b 布尔值数组。如果您足够幸运/足够小心,您最终可能不会遇到哈希冲突和精确测试。

【讨论】:

【参考方案3】:

数据结构

设 min 为目标集的最小值。 让 max 成为目标集的最大值。

设 bset 为位向量,大小为保持 max - min 所需的总位数。

对于目标集中的每个数字 x,令 n = x - min。设置集合中的第 n 位。

谓词

对于给定的数字 x,令 n = x - min,如果设置了第 n 位,则它是一个成员。

每个测试的成本应该是,1 次减法,2 次位移,一次数组访问和一次逻辑与。如果您的位向量由一组 32 位或 64 位值表示,则您可以通过在屏蔽位之前先测试该值是否非零,从概率上减少更多。

内存成本取决于最大值和最小值之间的差异。

根据输入集的大小,我也倾向于为您拥有的数字编译一个比较器。如果您即时编写二进制搜索,您会看到最坏情况下的 log2(n) 比较,它与最多约 32 个数字的二进制向量相当。

【讨论】:

【参考方案4】:

如果“静态和开始时间”表示编译时,您可以使用开关。

bool inGroup(int i) 
switch(i) 
case X1: return true;
case X2: return true;
// ...
case X7: return true;
default: return false;

如果数字没有在整数范围内广泛分布,这将导致跳转表。如果数字范围分布广泛,您可以计算最佳哈希函数,例如使用 gperf。

如果数字不是编译时常量,您应该考虑将它们存储在一个数组中,对其进行排序并使用二分查找。排序只需要进行一次,搜索将需要最大。三个比较。

【讨论】:

以上是关于快速排除成员是不是在集合中的主要内容,如果未能解决你的问题,请参考以下文章

在java中集合List,Set,Map,Properties的区别?

python中集合的特点和注意点?

C++拾取——stl标准库中集合交集并集差集对称差方法

C++拾取——stl标准库中集合交集并集差集对称差方法

Java——关于Java中集合的面试题

Java——12个关于Java中集合的面试题