统计二进制数组中1的个数--variable-precision SWAR算法解析

统计一个32位数字的二进制表示中,1的个数(把这个32位数字当做一个长度为32的二进制数组)。
如0xffc1, 其二进制为1111 1111 1100 0001, 那么1的个数为11个。

算法如下:

unsigned int swar(unsigned int n)                                                                                                                                            
{
    n = (n & 0x55555555) + ((n >> 1) & 0x55555555);  // line1

    n = (n & 0x33333333) + ((n >> 2) & 0x33333333);  // line 2

    n = (n & 0x0f0f0f0f) + ((n >> 4) & 0x0f0f0f0f);  // line3

    n = (n * 0x01010101) >> 24;                      // line4

    return n;
}

返回结果即为1的个数。

下面简要分析一个原理:
假定给出一个数字(32位二进制数字),0x20191124, 其二进制
0010 0000 0001 1001 0001 0001 0010 0100
2 0 1 9 1 1 2 4
共有8个1, 我们现在就是要求出这个8.
swar算法的思想就是把输入的二进制数组(数字)转换为有意义的数字(当然也可以使用临时变量存放,这些细节不作讨论)。
什么叫有意义的数字?
对于0x20191124,我们无法直接看出其二进制中1的个数,但是如果我们把它转换成
0x10121111, 这个数字是不就明显了?
什么?不明显?
你把0x10121111各个数字加起来看看,1 + 0 + 1 + 2 + 1 + 1 + 1 + 1 = 8;
0x10121111这个数字的每一位代表了原数中相应位置4位中1的个数,然后把这8个数字加起来就是32位数字中1的个数。
这就是上面说的有意义的数字。(原数字0x20191124我们是无法直接看出结果的)

line4, 就是在把各位数字加和。

先看第一行:

 n = (n & 0x55555555) + ((n >> 1) & 0x55555555); 

0x20191124先与上 0x55555555, 然后又移位与上 0x55555555,好像有点复杂。
我们一点点看,分析一下,
0x55555555的二进制 0101 0101 0101 0101 0101 0101 0101 0101 (奇数位为1)
0x20191124 & 0x55555555的结果不就是记录了 0x20191124的二进制奇数位是否为1么。
奇数位记录了,偶数位呢?这就是后半部分的意义。
n>>1错开一位, 再与上0x55555555不就是统计了偶数位是否为1么。
然后奇偶统计结果相加就是整个数字(0x20191124)中对1的统计结果(不是最终结果)。
0x20191124 & 0x55555555 结果a为:
0000 0000 0001 0001 0001 0001 0000 0100
奇数位上共有5个1

((n >> 1) & 0x55555555)结果b为:
0001 0000 0000 0100 0000 0000 0001 0000
偶数位上共有3个1

a,b相加之后:不要以为加完是0x20191124!
0001 0000 0001 0101 0001 0001 0001 0100
每2位为一个单位整理一下,
0 1 0 0 0 1 1 1 0 1 0 1 0 1 1 0 可以看到这8个1已经被统计得有规律了,规律就是结果存放在以两个二进制位的单位内。
现在的目标就是把这些数字以两个二进制位为单位进行相加,怎么相加呢?

下面补充一点小学数学的知识,以便大学发现规律,为了方便还是以16进制进行计算(16位)
0x1234 * 0x1111进行乘法运算
统计二进制数组中1的个数--variable-precision SWAR算法解析_第1张图片
写出结果a的那一列正是1 + 2 + 3 + 4,各位相加的结果。
只需要(0x1234*0x1111) >> 12, 结果a右边的(自动舍去,溢出),就可计算出各位相加的结果。
这也就是Line4的原理。
至于line2, 3读者可自行分析。

总结:
swar算法分两个步骤:

  1. 统计数字(二进制数组),转换为一个各位表示原数1的个数的数字。
  2. 把转换后的数字各位相加(每位表示相应位置1的个数)。

文章开头给出的算法是逐渐归纳,由2位为一个单位进行统计,再到4位(line2),再到8位(line3),最后line4进行各位相加。

其实根据上面两个步骤,完全可以实现简化版的算法,如下图,只不多会有更多限制(比如原本32位右移30位,只剩2位有意义的数字,结果最大只能为3)。
swar算法最后右移了24位剩余8位,最大可返回255(255个1),已经足够了。(最多32个1)
统计二进制数组中1的个数--variable-precision SWAR算法解析_第2张图片
以上为个人理解,如有错误,还望指正。

你可能感兴趣的:(算法)