快速统计正二进制数中一个个数

本文介绍了一种计算两个32位整数二进制表示中位差异的方法,并提供了一个高效的算法来统计异或结果中1的个数。

题目描述:

世界上有10种人,一种懂二进制,一种不懂。那么你知道两个int32整数m和n的二进制表达,有多少个位(bit)不同么?

public int countBitDiff(int m, int n) {
        int count=0;
        String s=Integer.toBinaryString(m^n);//异或后统计1的个数
        for(int i=0;i<s.length();i++)
            if(s.charAt(i)=='1')
                count++;
        return count;
    }

这个比较笨啊,查了一下可以快速统计正整数 dif 中1的个数。如下:

//统计一个整数dif含有多少个1;
    while(dif!=0){
            dif=dif&(dif-1);
            cnt++;
        }             

这段代码的精髓就是在这一句: dif=dif&(dif-1)

  那么这句语句到底起到什么作用呢?看下面的分析

  假设dif=X1X2……Xn-1Xn,其中Xi(1≤i≤n)为1或0

  不妨设Xi是最右边的1,那么dif就可以写成如下的形式

  dif=X1X2……Xi-1Xi0……0,其中(1≤i≤n),Xi后面有n-i个0

  因为Xi=1,所以dif=X1X2……Xi-110……0,其中(1≤i≤n),1后面有n-i个0

  则dif-1=X1X2……Xi-101……1,其中(1≤i≤n),0后面有n-i个1

  则difAnd (dif-1)=X1X2……Xi-100……0,其中(1≤i≤n),Xi-1后面有n-i+1个0

  因此,dif &(dif-1)的效果把最右边的1变成0

  在上面的代码中,每把最右边的1变成0,则统计数加1,直到所有的1变成0为止。

<think>我们已有多个引用提供了不同的方法来实现统计二进制数1个数。下面总结几种常见方法,并给出示例代码。 方法1:逐位检查(使用位右移和按位与) 方法2:利用num &= (num-1)去除最右边的1(更高效) 方法3:使用查表法(当数据量大时可以考虑,但这里暂不展开) 根据引用[5]中的代码,使用`num = num & (num-1)`可以快速去除最右边的1,直到num变为0,这样循环次数等于1个数,效率较高。 引用[5]的代码示例: ```cpp #include<iostream> using namespace std; int main() { int n; cin >> n; for(int i = 0; i < n; i++) { int x; scanf("%d", &x); int count = 0; while (x) { count++; x = x & (x - 1); // 去除最右边的1 } cout << count << ' '; } return 0; } ``` 引用[2]中使用了逐位检查的方法,但注意它统计的是0的个数,而且代码有误(它统计0的方式是当最低位不是1时计数,但是右移操作对于负数可能会陷入死循环,而且统计0的个数也没有考虑前导0)。我们可以借鉴其思路,但修改为统计1个数: ```cpp int countOnes(int num) { int count = 0; // 假设我们考虑32位整数 for (int i = 0; i < 32; i++) { if (num & 1) { count++; } num >>= 1; // 注意:对于负数,使用逻辑右移还是算术右移?这里用无符号整数更安全 } return count; } ``` 但是,引用[3]中给出了一种统计0的个数的方法:先统计1个数,然后用32减去。同样,我们可以借鉴来统计1个数: ```cpp int countOnes(int n) { int count = 0; for (int i = 0; i < 32; i++) { count += (n >> i) & 1; // 每次取第i位(从低位开始),然后与1进行与运算 } return count; } ``` 然而,对于有符号整数,右移时高位会补符号位,因此统计1个数时,上述循环移位32次的方法对于负数也是确的,因为我们需要统计所有位(包括符号位)。 但是,在引用[5]的方法中,使用`x & (x-1)`的方法有一个优点:循环次数等于1个数,对于1个数较少的情况效率很高。而且,这种方法不受整数位数限制(只要整数位数固定,循环次数最多为32次,但实际循环次数是1个数)。 因此,我们推荐使用`x & (x-1)`的方法。 另外,注意引用[4]提到了使用掩码的方法,但本质上与逐位检查类似。 下面,我们给出两种方法的实现,并讨论它们的优缺点。 方法一:使用`x = x & (x-1)` 优点:循环次数少(等于1个数),效率高。 缺点:对于32位整数,1个数最多32次,所以也是O(1)的时间复杂度,但实际运行比固定32次循环可能更快(当1个数少时)。 方法二:固定32次循环(逐位检查) 优点:稳定,容易理解。 缺点:无论1个数多少,都循环32次。 在实际应用中,可以根据情况选择。如果输入数据中1个数普遍较少,方法一更优;如果1个数很多,两种方法差别不大。 但是,注意方法一对于负数也是有效的吗?我们来验证: 对于负数,例如-1(在二进制中表示为全1),执行`x = x & (x-1)`: 初始:x = -1 (二进制1) 第一步:x-1 = -2,-1 & -2 = ... 实际上,-1二进制表示是0xFFFFFFFF,减1变成0xFFFFFFFE,按位与后得到0xFFFFFFFE(-2),计数1次。 然后x=-2,再减1变成0xFFFFFFFD,按位与:0xFFFFFFFE & 0xFFFFFFFD = 0xFFFFFFFC(-4),计数2次。 ... 直到最后变成0,一共32次(因为32位全1)。 所以,对于负数的处理也是确的。 但是,在C++中,对于有符号整数的位操作,标准规定:有符号整数的右移操作是实现定义的(通常算术右移)。而在使用`x & (x-1)`时,我们并不涉及右移,所以没有问题。 因此,我们推荐使用`x & (x-1)`的方法。 代码示例(参考引用[5]): ```cpp #include <iostream> using namespace std; int countOnes(int x) { int count = 0; while (x) { count++; x &= (x - 1); } return count; } int main() { int n; cin >> n; while (n--) { int x; cin >> x; cout << countOnes(x) << ' '; } return 0; } ``` 注意:当输入为0时,while循环不执行,输出0,确。 另外,引用[3]中使用了32次循环的方法,如果题目要求32位无符号整数,那么可以用固定位数循环。但这里题目没有特别说明,所以两种方法都可以。 但是,如果题目要求处理负数,并且要求统计的是补码表示中1个数,那么上述两种方法都可以(因为负数的补码表示中1个数也是我们关心的)。但是,如果输入是负数,方法一(x&(x-1))也是有效的,如上所述。 结论:我们推荐使用`x = x & (x-1)`方法。 现在,根据用户问题,他想要查找使用C++统计二进制数位中1个数的方法,我们给出以上解答。 相关问题: 1. 如何用C++统计二进制数中0的个数? 2. 上述方法中,`x & (x-1)`的原理是什么? 3. 除了上述方法,还有哪些高效的方法可以统计二进制1个数? 4. 在C++中,如何统计一个数的二进制表示中前导0的个数? 注意:由于引用[3]中已经提到,统计0的个数可以用32减去1个数(对于32位数),所以第一个问题可以这样回答:先统计1个数,然后用总位数(32)减去1个数,即可得到0的个数(包括前导0)。如果要去除前导0,则需要另外处理。 </think>### C++统计二进制数位中1个数实现方法 在C++中,统计二进制数位中1个数主要有以下几种高效方法: #### 方法1:使用 `x & (x-1)` 位运算(推荐) **原理**:每次操作消除最右边的1,循环次数等于1个数 **时间复杂度**:$O(k)$(k为1个数) **代码实现**: ```cpp #include <iostream> using namespace std; int countOnes(int n) { int count = 0; while (n) { count++; n &= (n - 1); // 消除最右边的1 } return count; } // 测试用例 int main() { cout << countOnes(5); // 二进制101 → 输出2 cout << countOnes(7); // 二进制111 → 输出3 cout << countOnes(0); // 输出0 } ``` **优势**:效率高,尤其适合1较少的情况[^5] #### 方法2:逐位移位检查 **原理**:循环移位并与1进行位与运算 **时间复杂度**:$O(32)$(固定32次循环) **代码实现**: ```cpp int countOnes(int n) { int count = 0; for (int i = 0; i < 32; i++) { count += (n >> i) & 1; // 检查第i位是否为1 } return count; } ``` **适用场景**:需精确控制检查位数时[^3] #### 方法3:STL库函数(C++20) **原理**:使用标准库函数 **代码实现**: ```cpp #include <bit> cout << popcount(0b101010); // 输出3 ``` **注意**:需C++20及以上版本支持 ### 方法对比 | 方法 | 时间复杂度 | 优点 | 缺点 | |--------------|--------------|-------------------------|--------------------| | `x & (x-1)` | $O(k)$ | 实际运行最快 | 原理较抽象 | | 移位检查 | $O(1)$ | 逻辑简单,兼容性好 | 固定32次循环 | | STL `popcount`| $O(1)$ | 语法简洁 | 需C++20支持 | ### 注意事项 1. **负数处理**:上述方法均支持负数(按补码表示处理),例如 `countOnes(-1)` 返回32 2. **零值处理**:输入0时所有方法均返回0 3. **边界情况**:对于32位整数,建议使用 `unsigned int` 避免符号位扩展问题 > 综合推荐:优先使用 `x & (x-1)` 方法,兼具高效性和兼容性。对于性能敏感场景,部分编译器内置 `__builtin_popcount()` 函数(GCC/Clang)可进一步优化[^5]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值