求一个数的二进制序列中1的个数(3种方法)

本文介绍三种方法来计算一个整数(包括正数和负数)在其二进制补码表示中1的个数。第一种方法仅适用于正数,通过模2除2的方式获取每位的值;第二种方法适用于所有数,但效率较低,需要循环32次;第三种方法最为高效,通过按位与操作去掉最右边的1,直到数变为0。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言:一个数要存储在计算机中,是以该数二进制补码形式存储的。

法1(不推荐使用):

思路:当我们想要得到一个十进制数的每一位时,通常是采用模10除10的方法,模10得到最低位,除10得到高位,一直重复。那么想要得到一个数的二进制的每一位时,也可以采用模2除2的方法。


//求一个数存储在二进制数中1的个数(补码形式存储,所以算的是该数二进制补码中1的个数)

int count_one1(int m)   //不适用于负数
{
	int count = 0;
	while (m)   
	{
		if (m % 2 == 1)   //最低位是1
		{
			count++;
		}
		m /= 2;  //处理完最低位后将其丢掉
	}
	return count;
}
int main()
{
	int n = 1;
	int count = count_one1(n);
	printf("%d\n", count);
	system("pause");
	return 0;
}

但我们发现,一个负数不论%2还是/2结果永远是负数,所以用上述代码求解1的个数时永远都是0个,所以此种方法只适用于正数。

法二(推荐使用):

思路:我们知道一个数的二进制&1可得该数二进制最低位是0还是1,那么在此基础上,只要我们每次判断完最低位后右移一位就可以得到此时最低位的值,移31位便可得到该数二进制中1的个数。

int count_one2(int m)  //必须要循环32次,效率太低
{
	int i = 0;
	int count = 0;
	for (i = 0; i < 32; i++)
	{
		if (((m >> i) & 1) == 1)
		{
			count++;
		}
	}
	return count;
}

int main()
{
	int n = -1;
	int count = count_one2(n);
	printf("%d\n", count);
	system("pause");
	return 0;
}

但是此种方法效率太低,如果一个数的二进制补码中1全部集中在低位,而高位无0,循环32次无疑拉低了程序的运行效率。

法3(推荐使用):

思路:我们发现一个数和该数减1后做按位与操作再赋给该数,即某数=某数&(某数-1),每执行一次,该数二进制最右边的1就会丢掉。那么我们便可以知道,只要一个数在变成0之前能执行多少次某数=某数&(某数-1)操作,该数的二进制便有多少个1。

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <windows.h>
int count_one3(int m)
{
	int count = 0;
	while (m)
	{
		m = m&(m - 1);
		count++;
	}
	return count;
}
int main()
{
	int n = -1;
	int count = count_one3(n);
	printf("%d\n", count);
	system("pause");
	return 0;
}

此方法既适用于负数,又大大提高了程序执行的效率。

<think>我们有两个任务:将十进制转换为二进制,并统计二进制中1个数。 我们可以分两步:先转换,再统计。但也可以一步完成,因为统计1个数不需要存储整个二进制字符串。 方法1:先转换再统计(使用字符串) 步骤: 1. 将十进制转换为二进制字符串(使用之前的方法,比如除2取余法)。 2. 遍历字符串,统计字符&#39;1&#39;出现的次方法2:直接统计(使用位运算) 我们可以使用位运算中的技巧:n & (n-1) 可以消除n的二进制表示中最右边的1。 这样,我们不断进行这个操作直到n变为0,操作的次就是1个数。 我们也可以使用循环移位,然后统计最低位是否为1。 下面分别给出两种方法的代码: 方法1:先转换再统计(适合需要二进制字符串的情况) 方法2:直接统计(效率更高,不需要转换整个字符串) 这里我们假设用户需要完整的二进制字符串并且同时需要1个数。因此,我们可以使用方法1,先得到二进制字符串,然后统计1个数。 但是,如果用户不需要二进制字符串,只需要1个数,那么方法2更优。 根据问题,用户要“将十进制转换为二进制,并统计二进制表示中1个数”,所以我们需要同时提供二进制字符串和1个数。 我们可以这样设计: 函1:string decToBinary(int n) 将十进制转换为二进制字符串(使用之前的方法) 函2:int countOnes(int n) 统计1个数(使用位运算) 但是,如果我们在转换字符串的过程中同时统计1个数,可以避免二次遍历字符串,但转换过程本身就需要遍历每一位(虽然这个遍历是除2的次,和位成正比),所以我们可以: 在除2取余的过程中,每得到一个,如果是1就计。 然而,注意:我们得到的余序列是逆序的,但计和顺序无关。 因此,我们可以在转换函中同时计。 但是,为了模块化,我们也可以分开写。这里我们提供两种方式: 方式A:一次转换,同时计(在转换循环中计) 方式B:先得到字符串,再计(这样需要遍历字符串一次) 另外,我们还需要考虑负的情况。对于负,在计算机中通常用补码表示。但是,题目没有明确说明,我们假设输入是非负整。 如果输入可能是负,我们需要考虑。根据引用[3]提到补码转换,但题目要统计1个数,对于负,我们通常统计其补码表示中的1个数。 由于问题没有明确,我们先处理非负整,然后扩展负的处理。 步骤: 1. 非负整: 转换:除2取余,逆序排列。 统计1个数:在取余过程中,每得到一个1就计。 2. 负: 我们可以将其转换为补码形式(固定位,比如32位),然后统计。 但是,问题没有指定位,我们通常用32位或64位。这里我们假设使用32位补码。 我们可以这样处理负: 将负转换为其补码表示:先得到其绝对值的二进制,然后取反,再加1。 但更简单的方法是:使用无符号整来重新解释负的位模式。 在C++中,我们可以使用无符号类型来避免符号扩展的问题。例如: unsigned int num = static_cast<unsigned int>(n); // 这样对于负,num就是其补码表示的无符号值 然后我们对这个无符号整进行转换和统计。 因此,我们可以统一处理:将输入整转换为无符号整,然后进行转换和统计。 但是,注意:题目要是十进制转二进制,而计算机中存储的是补码,所以对于负,我们想要的是其补码表示。 所以,我们使用无符号转换,然后进行转换,这样得到的二进制字符串就是补码形式(对于负)。 统计1个数也是补码表示中的1个数。 因此,我们修改函:参为int,但内部转换为unsigned int,然后处理。 但是,注意:如果输入是正,转换无符号后值不变。 另外,如果输入是0,要特殊处理吗?不用,因为0的二进制就是0,1个数为0。 考虑到位,我们通常固定为32位(或者根据系统,但为了可移植性,我们指定32位)。 所以,我们可以这样: unsigned int num = static_cast<unsigned int>(n); 然后使用32位的转换(因为无符号整32位,我们输出32位二进制)。 但是,如果用户不需要32位,而是希望根据实际有效位?但是题目要统计1个数,固定32位是可以的。 因此,我们设计两个函: string decToBinary32(int n) // 将整转换为32位二进制字符串 int countOnes32(int n) // 统计32位表示中1个数 但是,注意:countOnes32也可以直接使用位运算,不需要转换字符串。 下面分别实现: 转换函32位): 我们使用bitset可以最简单,但为了展示过程,我们也可以用循环。 使用bitset: #include <bitset> string binary = bitset<32>(n).to_string(); 然后统计1个数:遍历字符串,或者使用bitset的count方法。 所以,我们可以这样: 方法1(使用bitset): string binary = bitset<32>(n).to_string(); int count = bitset<32>(n).count(); // count方法返回1个数 方法2(自己实现): 使用循环32次,每次取第i位(从0到31),然后判断该位是否为1。 但是,题目要是转换十进制,我们这里已经用整在计算机中的二进制表示(补码)来直接处理了,这符合要。 所以,我们有两种实现方式: 方式A:使用bitset(简单高效) 方式B:手动循环(教学目的) 下面我们分别写。 另外,注意:用户输入可能是任何整,但我们的32位可以表示-2^31到2^31-1之间的整。如果超出?题目没有要,我们假设在范围内。 根据引用[5]的例子,输入的字范围是0到255,所以是8位。但我们的函32位。 我们可以根据用户需调整位,但题目要是“将十进制转为2进制”,没有指定位。所以,我们可以提供不同位的版本,或者由用户指定。 但是,问题描述中并没有明确,所以我们先按32位实现。 另外,统计1个数,我们也可以使用著名的位操作技巧: int count = 0; unsigned int num = static_cast<unsigned int>(n); while (num) { count++; num = num & (num - 1); // 清除最右边的1 } 这个循环次等于1个数,效率更高。 因此,我们可以这样: 函1:使用bitset生成二进制字符串 函2:使用位操作统计1个数(高效) 但是,为了展示,我们也可以在一个中完成,但这里我们分开。 代码示例: ```cpp #include <iostream> #include <bitset> #include <string> using namespace std; // 转换为32位二进制字符串 string decToBinary32(int n) { // 使用bitset将整转换为32位二进制,然后转换为字符串 bitset<32> b(n); return b.to_string(); } // 统计32位整二进制中1个数 int countOnes32(int n) { unsigned int num = static_cast<unsigned int>(n); int count = 0; while (num) { num = num & (num - 1); // 清除最低位的1 count++; } return count; } // 或者使用bitset的count方法 int countOnes32_2(int n) { bitset<32> b(n); return b.count(); } int main() { int n; cout << "请输入一个: "; cin >> n; string binary = decToBinary32(n); int ones = countOnes32(n); // 或者使用countOnes32_2 cout << "二进制表示: " << binary << endl; cout << "1个数: " << ones << endl; return 0; } ``` 注意:上面的二进制字符串是32位,没有空格,如果希望每8位加空格,可以修改字符串。 另外,统计1个数,我们有两种方法,都是正确的。 但是,如果输入是0,countOnes32会返回0,正确。 测试: 输入:8 -> 二进制: 00000000000000000000000000001000 -> 1个数: 1 输入:-1 -> 二进制: 11111111111111111111111111111111 -> 1个数: 32 负测试:-1的补码就是全1,所以32个1。 所以,这个程序可以处理正负。 如果用户希望不要前导0(比如正不显示前面的0),但这样位就不固定了。但是,题目要统计1个数,固定32位是可以的,因为前导0不影响1个数(0不会增加1个数)。但是,如果用户希望看到实际有效位,我们可以去掉前导0,但这样负的补码就不能去掉前导0(因为补码是固定位的)。 所以,我们按照32位输出,这样统一。 如果用户需要非固定位,我们可以用动态的方法(如方法1的除2取余,不固定位),但这样负处理会麻烦(需要补码)。 因此,我们这里采用32位固定。 另外,如果用户输入的超出了32位表示范围?在C++中,int通常就是32位,所以输入n本身就是32位的,不会超出。但long long是64位,如果用户输入long long,我们需要调整。但题目没有说,我们假设是int。 所以,上述代码可以满足要。 但是,问题要“参考站内引用”,我们引用了[2](除2取余)和[3](补码)以及[5](例子)。我们在回答中要标注引用。 另外,用户要“统计二进制表示中1个数”,我们提供了两种方法:位操作和bitset的count方法。 我们选择位操作的方法,因为效率高(循环次等于1个数,而不是32次)。 因此,最终代码: 注意:如果用户需要的是非补码表示(即只针对非负),那么我们需要判断。但问题没有明确,我们按照补码处理(因为这样统一,且符合计算机表示)。 如果用户要原码表示,那么负需要特殊处理(先输出负号,然后输出绝对值的二进制)。但题目要是“二进制”,在计算机领域通常指补码。 所以,我们按照补码处理。 下面给出完整代码,并添加注释。</think>在 C++ 中实现十进制转二进制并统计 1个数,可以通过位运算高效完成。以下是两种常用方法及其实现原理: ### 方法 1:位运算直接统计(高效) **原理**:使用 `n & (n - 1)` 操作消除最低位的 1,统计操作次即为 1个数 **时间复杂度**:$O(k)$(k 是 1个数),空间复杂度 $O(1)$ ```cpp #include <iostream> #include <bitset> using namespace std; int countOnes(int n) { unsigned num = static_cast<unsigned>(n); // 处理负补码 int count = 0; while (num) { num &= (num - 1); // 消除最低位的1 count++; } return count; } int main() { int decimal = -5; // 负示例(补码表示) cout << "十进制: " << decimal << endl; cout << "二进制: " << bitset<32>(decimal) << endl; cout << "1个数: " << countOnes(decimal) << endl; return 0; } ``` **输出示例**: ``` 十进制: -5 二进制: 11111111111111111111111111111011 1个数: 31 ``` ### 方法 2:转换字符串后统计(直观) **原理**:用 `bitset` 转换为二进制字符串,遍历统计 &#39;1&#39; 的个数 **时间复杂度**:$O(\log n)$,空间复杂度 $O(\log n)$ ```cpp #include <iostream> #include <bitset> #include <string> using namespace std; int countOnesByString(int n) { bitset<32> binary(n); // 32位二进制表示 string binStr = binary.to_string(); int count = 0; for (char c : binStr) { if (c == &#39;1&#39;) count++; } return count; } int main() { int decimal = 13; // 正示例 cout << "十进制: " << decimal << endl; cout << "二进制: " << bitset<8>(decimal) << endl; // 8位简写 cout << "1个数: " << countOnesByString(decimal) << endl; return 0; } ``` **输出示例**: ``` 十进制: 13 二进制: 00001101 1个数: 3 ``` ### 关键点说明 1. **负处理**:使用 `unsigned` 转换处理补码表示(引用[3]) 2. **位运算优化**:`n & (n-1)` 是高效统计 1 的核心技巧 3. **位控制**:`bitset<N>` 可指定输出位(N=8/16/32/64) 4. **边界情况**: - 输入 0:返回 0 个 1 - 负:统计补码中的 1(引用[4]) ### 性能对比 | 方法 | 时间复杂度 | 适用场景 | |------------|------------------|--------------------| | 位运算统计 | $O(\text{1个数})$ | 高性能需 | | 字符串统计 | $O(\text{位})$ | 需可视化二进制结果 |
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值