繁体   English   中英

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

[英]How to find number of 1's in a binary number in O(1) time?

我知道之前有人问过,但我正在看这里列出的特定解决方案:

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

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

它是如何工作的?

这里有什么警告吗?

理论上可以在恒定的时间内找到答案吗? 我的意思是,我们实际上不必迭代这些位来计算?

计数位

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

u = a 31 * 2 31 + a 30 * 2 30 + ... + a 0 * 2 0

我们想要a 31 + a 30 + ... + a 0

让我们比较一下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的值。 对于a 31 ,它是:

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

或者a 30

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

我们发现: q = a 31 * (2 31 - 1) + a 30 * (2 30 - 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)(λ+κ) * 2 0 + (κ+ι) * 2 3 + (ι+θ) * 2 6 + ...

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

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

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

迭代这些位恒定时间,因为类型中的位数是常数。

因此,检查一位掩码并针对目标值中的每个位移位的解决方案实际上是O(1) (例如,其中常数是32)。

最快的方法是使用popcnt指令。 您通常可以通过编译器内部访问它。 您的解决方案在缺少此指令的平台上非常有用。

并行计数位设置显示了如何完成。 该方法可用于8位,16位,32位,64位,128位等位字,但计算中使用的常数会发生变化。

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

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

至于它如何工作,它使用“魔术数字”。 有关说明,请参阅此新闻组帖子

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM