统计32位二进制数中有多少个1

本文深入解析三种计算算法的实现逻辑与数学基础:数字位数线性、1的个数线性及最神级方法。通过具体实例,详细解释每一行代码的意义,揭示算法的强大之处。了解这些技巧不仅有助于提升编程能力,还能为解决实际问题提供新的思路。

一、关于关于数字位数线性

for(n=0; b; b >>= 1) if (b & 1) n++;

二、关于"1"的个数线性

for(n=0; b; n++) b &= b-1;

三、最神级的方法

m = m - ((m>>1)&(033333333333)) - ((m>>2)&(011111111111));

n= ( (m + (m>>3)) & (030707070707) )   %(63);

下面解释这代码的含义:

对于第一行,先看3位二进制,如:5=101,我们把它表示成 abc,那么1的个数就是a+b+c

5=4a+2b+c;

(5>>1)&(03)=2a+b;

(5>>2)&(01)=a;

所以 5-(5>>1)&(03)-(5>>2)&(01) =a+b+c;

对于32位的树来讲,可以分为11组,每组3位,第一组是两位,所以

m-(m>>1)&(033333333333)-(m>>2)&(011111111111)所得的结果就是每3位中1的个数所组成的32位数

比如5=0...000101=>0...000010

现在我们知道了,只要把每3位中表示的1的个数相加就可以了n11+...+n2+n1,第一行所得的结果可以表示成

2^10*n11+...+2^1*n2+2^0*n1,

也许你会想到

m%7=n11+...+n2+n1

但是犯了一个致命的错误,如果所得结果是1的个数是14,那么14%7=0;错误!

所以这里采取先两个3位相加成6位,也就是下面的代码

(m+(m>>3))&(030707070707)

然后%63 就是所得结果。

为什么&(030707070707)呢?这是为了不受前面进位的影响,

为什么是m+(m>>3)呢?这是因为3位二进制表示的是3原来3位二进制数的1的个数,大小不超过3,

所以2两个3位数加起来小于7,不会产生进位,可以直接相加。

如果还有什么不理解的可以发表评论,谢谢!

这个算法太强悍了,在此感谢露神~

<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次(因为321)。 所以,对于负数的处理也是正确的。 但是,在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]。
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值