1.3 题解:计算无符号二进制数中1的个数

Chapter1: 位运算的奇技淫巧

3. 题解:计算无符号二进制数中1的个数

题目

计算无符号整数的二进制表示中1的个数

算法

移位统计法(普通法)

这个简单算法对于每一位都需要一次操作,直到结束。所以对于32位字长,且只有最高位为1时(即最坏情况),这个算法会操作32次。

#include
#include
using namespace std;

/**Problem: 计算无符号整数v的二进制中1的个数**/
/*移位统计法*/
int main(){
    
    unsigned int v=3; 
    unsigned int count=0; // 保存计算的结果
    
    for (;v!=0; v >>= 1)//将变量v进行移位,直到最高位的1被右移舍掉后(因此该算法对有符号数无效,移位其符号位为1)
    {
        count += (v & 1);//如果v的最低位为1,则(v&1)会返回1,否则为0(&运算是按位与) 
    }
    printf("%d\n",count);
    return 0;
}

快速法

这种方法速度比较快,其运算次数与输入n的大小无关,只与n中1的个数有关。

如果n的二进制表示中有k个1,那么这个方法只需要循环k次即可。

其原理是不断清除n的二进制表示中最右边的1,同时累加计数器,直至n为0。

v&(v-1) 得到的是 v 去掉最低位的1 后的数
为什么v &= (v – 1)能清除最右边的1呢?因为从二进制的角度讲,v相当于在v - 1的最低位加上1
比如 7(0111)= 6(0110)+ 1(0001),所以7 & 6 = (0111)&(0110)= 6(0110),清除了7的二进制表示中最右边的1(也就是最低位的1)
又比如 8(1000)= 7(0111)+ 1(0001),所以8 & 7 = (1000)&(0111)= 0(0000),清除了8最右边的1(其实就是最高位的1,因为8的二进制中只有一个1)

int bitCount2(unsigned int v){
    unsigned int count=0;
    for(;v!=0;count++){
        v&=(v-1);// v&(v-1)= v去掉最低位的1后的数 
    }
    return count; 
}
扩展

问题:用一条语句判断一个正整数是不是2的正整数次方

算法:

如果一个数 N2 的整数次方,即 N=2^n ,则说明 N 的二进制形式为 000...1...00 的形式,即只有 1 位是 1 ,其余位是 0N&(N-1) 返回的是去掉最低位的1的结果,如果结果为0,说明只有1个1

if(N&(N-1)==0

查表法

其实就是建立一张表,这张表存储了每一个 [0,255] 之间的数的二进制表示中 1 的个数,

对于32 bit 的无符号整数 unsigned int , 将其切分为 4 段,每段 8 bit ,查表分别求出每段的 1 的个数再求和即可。

动态建表是在程序运行时创建表,所以速度会慢一些,静态表则会快一些,都是在用空间换时间。

对于静态表,搞一个16 bit([0,2^16]) 的表,或者更极端一点 32 bit 的表,速度将会更快。

动态建表

由于表示在程序运行时动态创建的,所以速度上肯定会慢一些,把这个版本放在这里,有两个原因

  1. 介绍填表的方法,因为这个方法的确很巧妙。

  2. 类型转换,这里不能使用传统的强制转换,而是先取地址再转换成对应的指针类型。也是常用的类型转换方法。

填表原理:

对于任意一个正整数v,分奇数和偶数两种情况讨论:

  • 如果v是偶数,那么v的二进制中1的个数与v/2中1的个数是相同的。

    比如4和2的二进制中都有一个1,6和3的二进制中都有两个1。

    为啥?因为v是由v/2左移一位而来,而移位并不会增加1的个数。

  • 如果v是奇数,那么v的二进制中1的个数是v/2中1的个数+1。

    比如7的二进制中有三个1,7/2 = 3的二进制中有两个1。

    为啥?因为当v是奇数时,v相当于v/2左移一位再加1。

查表原理:

对于任意一个32位 无符号整数,将其分割为 4 部分,每部分 8 bit ,对于这四个部分分别求出 1 的个数,再累加起来即可。而 8bit 对应 2^8 = 256种01组合方式,这也是为什么表的大小为256的原因。

注意类型转换的时候,先取到 v 的地址,然后转换为 unsigned char* ,这样一个 unsigned int(4 bytes=32 bit) 对应四个 unsigned char(1 bytes=8 bit),分别取出来计算即可。

举个例子,以87654321(十六进制)为例,先写成二进制形式, 8bit一组,共4组,这4组中1的个数分别为4,4,3,2,所以一共是13个1,如下面所示。

10000111 01100101 01000011 00100001 = 4 + 4 + 3 + 2 = 13

/*动态查表法*/
int bitCount3(unsigned int v){
    
    //建表 
    unsigned char bitsSetTable[256]={0};
    
    //初始化表 
    for(int i=0;i<256;i++){
        bitsSetTable[i]=bitsSetTable[i/2]+(i&1);
    }
    
    unsigned int count=0;
    
    //查表
    unsigned char *p=(unsigned char*) &v;
    
    //p[0]为 v二进制表示的低位的后8位对应的数值,...,p[3]为高位前8位对应的值 
    //bitsSetTable[p[0]]即为p[0]值对应的二进制的1的个数 
    count = bitsSetTable[p[0]]+
            bitsSetTable[p[1]]+
            bitsSetTable[p[2]]+
            bitsSetTable[p[3]];
            
    return count;
}
静态建表

首先构造一个包含256个元素的表 tabletable[i] 即数值 i 的二进制 1 的个数,这里的 i[0,255] 之间任意一个值。

对于任意一个 32bit 无符号整数 n ,我们将其拆分成4个 8bit ,然后分别求出每个8bit1 的个数,再累加求和即可.

这里用移位的方法,每次右移8位,并与 0xff(11111111) 相与,取得最低位的 8bit (高位的与0相与所以结果为0),查表得到对应的 1 的个数,累加后继续移位,如此往复,直到v为0。

所以对于任意一个32位整数,需要查表4次。

以十进制数 2882400018 为例,其对应的二进制数为 10101011110011011110111100010010,对应的4次查表过程如下:红色表示当前8bit ,绿色表示右移后高位补零。

第一次(v & 0xff) 101010111100110111101111 00010010

第二次((v >> 8) & 0xff) 00000000 1010101111001101 11101111

第三次((v >> 16) & 0xff)00000000 00000000 10101011 11001101

第四次((v >> 24) & 0xff)00000000 00000000 00000000 10101011

/*静态建表法*/
int bitCount4(unsigned int v){
{ 
    unsigned int table[256] = 
    { 
        0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 
        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 
        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 
        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 
        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 
        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 
        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 
        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 
        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 
        4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 
    }; 

    int count=0;
    count = table[v&0xff]+table[(v>>8)&0xff]+
            table[(v>>16)&0xff]+table[(v>>24)&0xff];    
}
} 

参考资料

[1] 算法-求二进制数中1的个数

[2] 位运算的奇技淫巧:Bit Twiddling Hacks

你可能感兴趣的:(1.3 题解:计算无符号二进制数中1的个数)