求一个有符号整数二进制补码中1的个数

方法一:逐位检查

这种方法通过不断地将整数右移,同时检查最低位的值来统计其中的1的个数。

具体步骤如下:

  1. 初始化一个计数器变量 count,用于记录1的个数。
  2. 循环执行以下步骤,直到整数变为0:
    1.  检查整数的最低位是否为1(通过使用 & 操作符和掩码 1)。
    2.  如果最低位为1,则增加计数器 count 的值。
    3.  将整数右移一位(使用 >> 操作符)。
  3. 返回计数器变量 count 的值。

主要利用一个数 &1 运算可以获得这个数的最后一位的二进制(在二进制中,要么在该位是0,&1运算后为0;要么在该位是1,&1运算后为1),再利用右移遍历该数的每一位二进制进行统计出现1的个数。

代码:

int countOnes(int num) {
    int count = 0;
    while (num != 0) {
        if (num & 1) {
            count++;
        }
        num >>= 1;
    }
    return count;
}

这种方法有瑕疵,后面会讲。

方法二:Brian Kernighan 算法

这种方法利用了一个性质,即对于任何整数 xx & (x - 1) 可以将 x 最低位的1变为0。基于这个性质,我们可以在每次执行 x & (x - 1) 操作时,消除掉一个1,从而计算1的个数。

具体步骤如下:

  1. 初始化一个计数器变量 count,用于记录1的个数。
  2. 循环执行以下步骤,直到整数变为0:
    1.  做 x = x & (x - 1) 操作。
    2.  增加计数器 count 的值。
  3. 返回计数器变量 count 的值。

举个例子:计算 -1 的二进制中出现1的个数

& 计算的是数据的二进制补码,我们知道 -1的补码是全1,而int类型4个字节32位,所以-1的补码是:1111 1111 1111 1111 1111 1111 1111 1111

①count++变成1,x = (1111 1111 1111 1111 1111 1111 1111 1111)& (1111 1111 1111 1111 1111 1111 1111 1110)= 1111 1111 1111 1111 1111 1111 1111 1110;

②count++变成2,x = (1111 1111 1111 1111 1111 1111 1111 1110)& (1111 1111 1111 1111 1111 1111 1111 1100)=  1111 1111 1111 1111 1111 1111 1111 1100;

……

我们可以看到,每循环执行一次,数据的二进制中的1会被消除掉。

代码:

int countOnes(int num) {
    int count = 0;
    while (num != 0) {
        num = num & (num - 1);
        count++;
    }
    return count;
}

注:

对于移位操作符>>、<< 来说,移动的都是数据的补码。

  • 右移操作符 >>

右移有两种情况:

①逻辑右移(右边丢弃,左边直接补0)  

②算术右移(右边丢弃,左边补符号位)

C语言没有明确规定右移运算是指逻辑右移还是算术右移,通常编译器采用的是算术右移。

  • 左移操作符  <<

左边丢弃,右边直接补0

方法一的瑕疵:

当利用方法一实现求-1的补码中的二进制时,a>>-1是属于标准未定义行为,因为右移>>有两种情况:

  • 如果是逻辑右移(右边丢弃,左边直接补0),会求得-1补码中出现1的个数是32个;
  • 如果是算术右移(右边丢弃,左边补符号位),有符号数右移运算高位是补符号位的,负数的符号位是1,所以x永远不会变为0,是个死循环,不能计算出正确的结果。

所以综合来看,方法二是解答本问题较稳妥的方法。


本次内容到此结束了!如果你觉得这篇博客对你有帮助的话 ,希望你能够给我点个赞,鼓励一下我。感谢感谢……

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