32bit的无符号数字中1的个数

swar算法

uint32_t swar(uint32_t i){
    i = (i & 0x55555555) + ((i>>1) & 0x55555555);  // 步骤1
    i = (i & 0x33333333) + ((i>>2) & 0x33333333);  // 步骤2
    i = (i & 0x0F0F0F0F) + ((i>>4) & 0x0F0F0F0F);  // 步骤3
    i = (i * 0x01010101) >> 24;                    // 步骤4
    return i;
}

把i的二进制位理解成:长度为32的数组,每个元素取值区间[0,1],每个元素正好能代表这个位是不是1.
问题转化为:求这个数组的和,也就是1的个数

规定:一个32bit数字,从右到左,依次是第1位,第2位,…,第32位 (虽然跟计算机的标记有出入)
i & 0x55555555 = i & 0b 0101 0101 0101 0101 0101 0101 0101 0101 把奇数位剥离出来
i & 0xaaaaaaaa = i & 0b 1010 1010 1010 1010 1010 1010 1010 1010 把偶数位剥离出来
i = i & 0x55555555 + i & 0xaaaaaaaa 看证明:
i = i & (0x55555555 + 0xaaaaaaaa) = i & 0xffffffff = i
偶数位剥离出来后,第1位、第3位、…、第31位铁定为0
将剥离出来的偶数位转换为奇数位,变成了 (i>>1) & 0x55555555,这个结果会让偶数位的1都跑到奇数位上去

这样 (i & 0x55555555) 、 (i>>1) & 0x55555555 的偶数位都为0,奇数位都是i之前的bit
(i & 0x55555555) 瓜分了i的所有奇数位
(i>>1) & 0x55555555 瓜分了i的所有偶数位,瓜分完后对齐到新的32bit的奇数位上

(i & 0x55555555) + (i>>1) & 0x55555555 后,1的个数和全部落在每2bit一个单元的块上
相加之后,每一个单元块上,1的个数最多为2,可用2bit全部表示,但最大值为 0b10

同理:
(i & 0x33333333) + ((i>>2) & 0x33333333) 之后,1的个数和全部落在每4bit一个单元的块上
相加之后,每一个单元块上,1的个数最多为4,可用3bit全部表示,但最大值为 0b0100

(i & 0x0F0F0F0F) + ((i>>4) & 0x0F0F0F0F) 之后,1的个数和全部落在每8bit一个单元的块上
相加之后,每一个单元块上,1的个数最多为8,可用4bit全部表示,但最大值为 0b1000

这样,每8bit中,高4bit全是0,低4bit表示这8bit中1的个数

所以才能对这8bit直接相加

最后将每字节的数字相加,就是1的个数和

i = (i * 0x01010101) >> 24 = (i * 0x01000000 + i * 0x00010000 + i * 0x00000100 + i * 0x00000001) >> 24
  = (i * 2^0 + i * 2^8 + i * 2^16 + i * 2^24) >> 24
  = (i + i << 8 + i << 16 + i << 24) >> 24
  = i >> 24 + (i << 8) >> 24 + (i << 16) >> 24  + (i << 24) >> 24
这里左移右移的目的在于只把当前8bit的数字留下,所有高8bit的数字全部清0
所以才能直接相加
i >> 24			:留下了最高8bit
(i << 8) >> 24	:将最高8bit清0,留下了次高8bit
(i << 16) >> 24 :将最高16bit清0,留下了次低8bit
(i << 24) >> 24 :将最高24bit清0,留下了最低8bit

这里用到了分治法的思想。

扩展

i = (i & 0x0F0F0F0F) + ((i>>4) & 0x0F0F0F0F) 之后,1的个数和全部落在每8bit一个单元的块上
相加之后,每一个单元块上,1的个数最多为8,可用4bit全部表示,但最大值为 0b1000 = 2^3

可以继续往下分治:
i = (i & 0x00FF00FF) + ((i>>8) & 0x00FF00FF) 之后,1的个数和全部落在每16bit一个单元的块上
相加之后,每一个单元块上,1的个数最多为16,可用5bit全部表示,但最大值为 0b10000 = 2^4

i = (i & 0x0000FFFF) + ((i>>16) & 0x0000FFFF) 之后,1的个数和全部落在每32bit一个单元的块上
相加之后,每一个单元块上,1的个数最多为32,可用6bit全部表示,但最大值为 0b100000 = 2^5

此时 i 的最低8位存储的就是最初的i中的1的个数

实例

#include <stdio.h>

unsigned int swar1(unsigned int i){
    i = (i & 0x55555555) + ((i>>1) & 0x55555555);
    i = (i & 0x33333333) + ((i>>2) & 0x33333333);
    i = (i & 0x0F0F0F0F) + ((i>>4) & 0x0F0F0F0F);
    i = (i * 0x01010101) >> 24;
    return i;
}

unsigned int swar2(unsigned int i){
    i = (i & 0x55555555) + ((i>>1) & 0x55555555);
    i = (i & 0x33333333) + ((i>>2) & 0x33333333);
    i = (i & 0x0F0F0F0F) + ((i>>4) & 0x0F0F0F0F);
    i = (i & 0x00FF00FF) + ((i>>8) & 0x00FF00FF);
	i = (i & 0x0000FFFF) + ((i>>16) & 0x0000FFFF);
    return i;
}

int main()
{
   printf("Hello, World!  %d\n", swar1(0xffffffff));
   printf("Hello, World!  %d\n", swar2(0xffffffff));
   return 0;
}

其他算法

unsigned int count3(unsigned int i){
	int count = 0;
	while (i) {
		i &= (i-1);
		count++;
	}
    return count;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值