如何在O(1)时间内找到二进制数的1?

时间:2013-11-01 15:05:42

标签: c algorithm bit-manipulation

我知道之前已经问过这个问题,但我正在查看列出here的这个特定解决方案:

int BitCount(unsigned int u)
{
     unsigned int uCount;

     uCount = u - ((u >> 1) & 033333333333) - ((u >> 2) & 011111111111);
     return ((uCount + (uCount >> 3)) & 030707070707) % 63;
}

它是如何运作的?

这里有什么警告吗?

理论上可以在恒定时间内找到答案吗?我的意思是,我们实际上不是遍历要计数的位吗?

4 个答案:

答案 0 :(得分:8)

计数位

无符号32位整数u可以这样写:

u = a31 * 231 + a30 * 230 + ... + a0 * 20

我们想要a31 + a30 + ... + a0

的值

让我们比较u >> k

的值
u >> 0  = a31 * 231 + a30 * 230 + ... + a1 * 21 + a0 * 20
u >> 1  = a31 * 230 + a30 * 229 + ... + a1 * 20
u >> 2  = a31 * 229 + a30 * 228 + ...
...
u >> 29 = a31 * 22  + a29 * 21  + ...
u >> 30 = a31 * 21  + a30 * 20 
u >> 31 = a31 * 20 

我们将通过以下公式计算比特数:

u >> 0 - u >> 1 - u >> 2 - ... - u >> 31 = p

让我们看看为什么会这样:

  u >> 0 - u >> 1 - u >> 2 - ... - u >> 31
= u >> 0 - (u >> 1 + u >> 2 + ... + u >> 31)
= u - q

q的价值是多少?让我们一点一点地计算它,看看上面u >> k的值。对于a31,它是:

  a31 * 230 + a31 * 229 + ...
= a31 * (230 + 229 + ...)
= a31 * (231 - 1)

a30

  a30 * 229 + a30 * 228 + ...
= a30 * (229 + 228 + ...)
= a30 * (230 - 1)

我们发现:q = a31 * (231 - 1) + a30 * (230 - 1) + ...

因此

u - q = a31 * 231 - a31 * (231 - 1) + ...
      = a31 + a30 + ... + a0

以3位块计数位

这个算法从做同样的事情开始,但是以3比特的块为单位:

u >> 0                = AaBbbCccDddEeeFffGggHhhIiiJjjKkk (each letter is a bit)
u >> 1 & 033333333333 =  A Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk (blank = zero)
u >> 2 & 011111111111 =     B  C  D  E  F  G  H  I  J  K

根据上述算法的不同,uCount中的每个八位字节都包含u中相应八位字节中设置的位数。

uCount      =   αβγδεζηθικλ (each greek letter is an octet)
uCount >> 3 =    αβγδεζηθικ

所以uCount + (uCount >> 3)(λ+κ) * 20 + (κ+ι) * 23 + (ι+θ) * 26 + ...

通过与0o30707070707进行AND运算,我们将每隔一个八位字节屏蔽掉,这样我们只计算每对一次:

r = (λ+κ) *  20 + (ι+θ) *  26 + (η+ζ) *  212 + ...
  = (λ+κ) * 640 + (ι+θ) * 641 + (η+ζ) * 642  + ...

这是一个base-64号码,我们想要总结64位数字来获得α+β+γ+δ+ε+ζ+η+θ+ι+κ+λ,这是我们的最终结果。为此,我们计算其base-64 digital root:知道结果永远不会大于32,我们只需将数字模数为63.

答案 1 :(得分:5)

迭代位常量时间,因为类型中的位数是常量。

因此,检查一位掩码并为目标值中的每个位移位的解决方案确实是O(1)(例如,常量为32的位置)。

答案 2 :(得分:5)

执行此操作的最快方法是使用popcnt指令。您通常可以通过compiler intrinsic访问它。您的解决方案在缺少此指令的平台上非常有用。

答案 3 :(得分:1)

Counting bits set, in parallel显示了这是如何完成的。该方法可用于8位,16位,32位,64位,128位等位字,但计算中使用的常数会发生变化。

当我们说这个操作是O(1)时,我们的意思是它可以在恒定时间内完成而不管字大小。计算天真的方式是O(n)in位数。

实际上,当处理器本身可以使用字大小时,这只是O(1)。

至于它如何运作,它使用“魔术数字”。有关说明,请参阅this newsgroup post