查找数字中有多少位

时间:2012-12-11 15:44:27

标签: algorithm language-agnostic

我知道如何使用掩码和按位运算符找出给定数字中有多少位(或布尔arra中有多少个元素),遍历所有位,检查它们是否打开。假设该数字具有任意长度,则该算法在O(n)时间内运行,其中n是该数字中的位数。是否有渐近更好的算法?我不认为这可能,但我怎么能正式证明呢?

7 个答案:

答案 0 :(得分:23)

Bit Twiddling Hacks提供了许多方法,包括以下方法:

  

计数位设置,Brian Kernighan的方式

unsigned int v; // count the number of bits set in v
unsigned int c; // c accumulates the total bits set in v
for (c = 0; v; c++)
{
  v &= v - 1; // clear the least significant bit set
}
     

Brian Kernighan的方法经历了尽可能多的迭代   设置位。因此,如果我们有一个只有高位设置的32位字,那么   它只会循环一次。

行动中的算法示例:

128 == 10000000 2 ,1位设置

128 & 127 ==   0    10000000 & 01111111 == 00000000

177 == 10110001 2 ,4位设置

177 & 176 == 176    10110001 & 10110000 == 10110000
176 & 175 == 160    10110000 & 10101111 == 10100000
160 & 159 == 128    10100000 & 10011111 == 10000000
128 & 127 ==   0    10000000 & 01111111 == 00000000

255 == 11111111 2 ,8位设置

255 & 254 == 254    11111111 & 11111110 == 11111110
254 & 253 == 252    11111110 & 11111101 == 11111100
252 & 251 == 248    11111100 & 11111011 == 11111000
248 & 247 == 240    11111000 & 11110111 == 11110000
240 & 239 == 224    11110000 & 11101111 == 11100000
224 & 223 == 192    11100000 & 11011111 == 11000000
192 & 191 == 128    11000000 & 10111111 == 10000000
128 & 127 ==   0    10000000 & 01111111 == 00000000

对于算法复杂度的语言不可知问题,不可能比O( n )做得更好,其中 n 是位数。任何算法都必须检查数字中的所有位。

当你不小心 n 的定义并让 n 成为“位移/屏蔽指令的数量”或某些此类时,这是多么棘手的问题。如果 n 是位数,则即使是简单的位掩码(&)也已经是O( n )操作。

那么,这可以比O( n )位测试更好吗?第
可以在少于O( n )的添加/移位/掩码操作中完成吗?是。

答案 1 :(得分:7)

我总是使用这个:

int
count_bits(uint32_t v)
{
        v = v - ((v >> 1) & 0x55555555);
        v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
        return ((v + (v >> 4) & 0xf0f0f0f) * 0x1010101) >> 24;
}

你必须知道整数的大小。

http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel

答案 2 :(得分:4)

Brian Kerninghan算法计算1位。

unsigned int v; // count the number of bits set in v
unsigned int c; // c accumulates the total bits set in v
for (c = 0; v; c++)
{
  v &= v - 1; // clear the least significant bit set
}

在这里阅读这个以及其他令人讨厌的黑客攻击:Bit-twiddling hacks

答案 3 :(得分:3)

执行此计算的最快方法是使用表数组edx [bl],其中bl寄存器包含字节值。如果数字是单个字节,则答案是一条指令:

 mov eax, [edx:bl]

如果数字中有多个字节(比如ebp指向的数组),则循环遍历字节(其中ecx是包含数字的数组中的字节数):

    sub ecx, 1
    mov eax, 0
 DoNextByte:
    mov bl, [ebp:ecx]
    add eax, [edx:bl]
    test ecx, ecx
    jz Done:
    sub ecx, 1
    jmp DoNextByte:
 Done:
    ; answer is in eax

这是绝对最快的方法,并且比任何数学计算都要快。请注意,Art解决方案中的移位指令非常昂贵。

Kernighan解决方案的问题在于,即使在汇编中手动编码,它也比我的算法慢。如果它被编译为C,它可能会产生大量的内存访问,即使超出了它所需的大量时钟周期,也会减慢它的速度。

请注意,如果在此指令旁边内联字节到计数映射,则整个数据表将位于CPU缓存中,因此它将非常快。在这种情况下,任何C程序都不会接近(比较慢20倍或更多)。

答案 4 :(得分:1)

那么,您也可以使用一个查找表,为每个字节保存#bits,然后将数字除以字节,将查找值相加。

它仍然是O(位数)但是因素很小。

答案 5 :(得分:1)

我认为你正在寻找的形式是一种“对抗性证明”。

假设一个算法A的运行速度比O(n)快。那么对于足够大的n,A一定不要看一点i。然后我们声称A必须是不正确的。除了比特i的相反值之外,“对手”将给出两个字符串s1和s2,它们是相同的。算法A将为s1和s2返回相同的值,因此对手已经“欺骗”A给出了错误的答案。因此,不存在在o(n)时间内运行的正确算法A.

答案 6 :(得分:0)

好的,这里似乎有一些关于订单统计,渐近符号,“大O”的混淆。

Brian Kernighan的算法在操作次数方面更好是正确的。但是,不正确 渐近更好。

可以从definition of big-O看到。

回想一下,当存在函数 g(n)时,函数是 O(f(n)) f(n)≤当 n 增长足够大时,kg(n)

现在,让我们将 w 定义为单词中设置的位数,并进一步注意单个单词的运行时间(如上所述)是数字的函数。的位设置。调用该函数 c(w)。我们知道有一个固定的字宽,称之为 ww ;显然,对于任何一个词,0≤ c(w) ww ,当然,最坏的情况是 c(w) = C(湿重)。因此,该算法的运行时间最差为 n c(ww)

因此,对于 n ,运行时间≤ n c(ww);也就是说, n n c(ww),因此根据定义,该算法具有O(n)的渐近最坏情况运行时间。