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