有效地找到与位掩码匹配的第一个元素
我有一个 N 64 位整数列表,其位代表小集合。每个整数最多有 k 位设置为 1。给定一个位掩码,我想找到列表中与掩码匹配的第一个元素,即 element &掩码==元素
。
示例:
如果我的列表是:
index abcdef
0 001100
1 001010
2 001000
3 000100
4 000010
5 000001
6 010000
7 100000
8 000000
并且我的掩码是111000
,则与掩码匹配的第一个元素位于索引 2 处。
方法 1:
线性搜索浏览整个列表。这需要 O(N) 时间和 O(1) 空间。
方法 2:
预先计算所有可能掩码的树,并在每个节点保留该掩码的答案。这需要 O(1) 的查询时间,但占用 O(2^64) 的空间。
问题:
如何才能以比 O(N) 更快的速度找到与掩码匹配的第一个元素,同时仍使用合理的空间量?我可以在预计算上花费多项式时间,因为会有很多查询。关键是k很小。在我的应用程序中,k <= 5,N 为数千。 mask有很多1;您可以假设它是从 64 位整数空间中统一提取的。
更新:
以下是一个示例数据集和一个在 Linux 上运行的简单基准程序:http://up.thild.com/binmask.tar.gz。对于 large.in
,N=3779 和 k=3。第一行是N,后面是代表元素的N无符号64位整数。使用 make
进行编译。使用 ./benchmark.e >large.out
运行以创建真实的输出,然后您可以对其进行比较。 (掩码是随机生成的,但随机种子是固定的。)然后用您的实现替换 find_first()
函数。
简单的线性搜索比我预期的要快得多。这是因为k很小,因此对于随机掩码来说,平均很快就能找到匹配项。
I have a list of N 64-bit integers whose bits represent small sets. Each integer has at most k bits set to 1. Given a bit mask, I would like to find the first element in the list that matches the mask, i.e. element & mask == element
.
Example:
If my list is:
index abcdef
0 001100
1 001010
2 001000
3 000100
4 000010
5 000001
6 010000
7 100000
8 000000
and my mask is 111000
, the first element matching the mask is at index 2.
Method 1:
Linear search through the entire list. This takes O(N) time and O(1) space.
Method 2:
Precompute a tree of all possible masks, and at each node keep the answer for that mask. This takes O(1) time for the query, but takes O(2^64) space.
Question:
How can I find the first element matching the mask faster than O(N), while still using a reasonable amount of space? I can afford to spend polynomial time in precomputation, because there will be a lot of queries. The key is that k is small. In my application, k <= 5 and N is in the thousands. The mask has many 1s; you can assume that it is drawn uniformly from the space of 64-bit integers.
Update:
Here is an example data set and a simple benchmark program that runs on Linux: http://up.thirld.com/binmask.tar.gz. For large.in
, N=3779 and k=3. The first line is N, followed by N unsigned 64-bit ints representing the elements. Compile with make
. Run with ./benchmark.e >large.out
to create the true output, which you can then diff against. (Masks are generated randomly, but the random seed is fixed.) Then replace the find_first()
function with your implementation.
The simple linear search is much faster than I expected. This is because k is small, and so for a random mask, a match is found very quickly on average.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
后缀树(位)可以解决这个问题,原始优先级位于叶节点:
如果在掩码中设置了位,则搜索两个臂,如果没有,则仅搜索 0 个臂;你的答案是你在叶节点遇到的最小数字。
您可以通过不按顺序而是通过最大可辨别性遍历位来(稍微)改进这一点;在您的示例中,请注意 3 个元素设置了位 2,因此您将
在示例掩码中创建这没有帮助(因为您必须遍历 bit2==0 和 bit2==1 两侧,因为您的掩码设置为位 2),但平均而言,它将改善结果(但以设置和更复杂的数据结构为代价)。如果某些位比其他位更有可能被设置,那么这可能是一个巨大的胜利。如果它们在元素列表中非常接近随机,那么这根本没有帮助。
如果您坚持使用基本上随机的位集,您应该从后缀树方法中获得平均
(1-5/64)^32
的好处(加速 13 倍),这可能< /em> 优于由于使用更复杂的操作而导致的效率差异(但不要指望它 - 位掩码很快)。如果列表中的位是非随机分布的,那么您几乎可以做得很好。A suffix tree (on bits) will do the trick, with the original priority at the leaf nodes:
where if the bit is set in the mask, you search both arms, and if not, you search only the 0 arm; your answer is the minimum number you encounter at a leaf node.
You can improve this (marginally) by traversing the bits not in order but by maximum discriminability; in your example, note that 3 elements have bit 2 set, so you would create
In your example mask this doesn't help (since you have to traverse both the bit2==0 and bit2==1 sides since your mask is set in bit 2), but on average it will improve the results (but at a cost of setup and more complex data structure). If some bits are much more likely to be set than others, this could be a huge win. If they're pretty close to random within the element list, then this doesn't help at all.
If you're stuck with essentially random bits set, you should get about
(1-5/64)^32
benefit from the suffix tree approach on average (13x speedup), which might be better than the difference in efficiency due to using more complex operations (but don't count on it--bit masks are fast). If you have a nonrandom distribution of bits in your list, then you could do almost arbitrarily well.这就是按位 Kd 树。每次查找操作通常需要少于 64 次访问。目前,旋转位(维度)的选择是随机的。
更新:
我对枢轴选择进行了一些实验,倾向于具有最高辨别值(“信息内容”)的位。这涉及到:
结果:随机主元选择表现更好。
This is the bitwise Kd-tree. It typically needs less than 64 visits per lookup operation. Currently, the selection of the bit (dimension) to pivot on is random.
UPDATE:
I experimented a bit with the pivot-selection, favouring bits with the highest discriminatory value ("information content"). This involves:
The result: the random pivot selection performed better.
构造一棵二叉树如下:
这样就将每个数字插入到数据库中。
现在,进行搜索:如果掩码中的相应位为 1,则遍历两个子节点。如果为0,则只遍历左节点。本质上继续遍历树,直到到达叶节点(顺便说一句,0 是每个掩码的命中!)。
该树的空间要求为 O(N)。
例如 1 (001)、2(010) 和 5 (101) 的树
Construct a a binary tree as follows:
This way insert every number in the database.
Now, for searching: if the corresponding bit in the mask is 1, traverse both children. If it is 0, traverse only the left node. Essentially keep traversing the tree until you hit the leaf node (BTW, 0 is a hit for every mask!).
This tree will have O(N) space requirements.
Eg of tree for 1 (001), 2(010) and 5 (101)
使用预先计算的位掩码。形式上仍然是 O(N),因为与掩码操作是 O(N)。最后一遍也是 O(N),因为它需要找到最低的位集,但这也可以加快。
With precomputed bitmasks. Formally is is still O(N), since the and-mask operations are O(N). The final pass is also O(N), because it needs to find the lowest bit set, but that could be sped up, too.