整数二进制展开中数位1的总数

这篇博客介绍了邓俊辉老师书中的两种算法,用于计算整数二进制展开中1的个数。第一种算法通过移位操作消除最低位的1,复杂度为O(1的个数)。第二种算法利用特定的MASK数进行计算,复杂度为O(log log n),展示了对二进制位操作的巧妙运用。

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

整数二进制展开中数位1的总数


前言

目前在,额,怎么说呢,回炉重造数据结构,今天在邓俊辉老师的书上看到了一个很有意思的算法,第一遍没看明白,后面仔细想了一下才搞明白什么意思,觉得很有趣,记录一下。
这个算法如题所言,统计整数二进制展开中数位1的总数
,网上常见的算法是通过将数n二进制展开中的最低位1转置0来统计,此算法本文也会介绍(说是介绍其实是抄书啦),然邓老师书上又介绍了一个更有趣的算法,这个会介绍本人的见解。


设输入一个数n,位宽W=log n.

1.第一种算法

先上代码

0001 int countOnes1 ( unsigned int n ) { //统计整数二进制展开中数位1的总数:O(ones)正比于数位1的总数
0002    int ones = 0; //计数器复位
0003    while ( 0 < n ) { //在n缩减至0之前,反复地
0004       ones++; //计数(至少有一位为1)
0005       n &= n - 1; //清除当前最靠右的1
0006    }
0007    return ones; //返回计数
0008 } //等效于glibc的内置函数int __builtin_popcount (unsigned int n)
<think>我们有一个任务:在C语言中编写一个函数,该函数计算一个二进制数中1个数,并判断该个数的奇偶性。 根据引用[4]提供了计算二进制1个数的几种方法,我们可以选择其中一种。 另外,判断奇偶性可以用引用[2]的方法:用`x & 1`来判断,如果结果为1则为奇数,0则为偶数。 步骤: 1. 编写一个函数,计算一个整数二进制表示中1个数。 我们可以使用引用[4]中的方法,例如使用`while (a & (a-1))`来不断消除最低位的1,直到a变为0,同时计数。 2. 得到1个数后,判断这个个数的奇偶性:用`count & 1`,如果结果为1,则个数为奇数;否则为偶数。 但是,我们也可以不用先计算1个数再判断奇偶,因为奇偶性实际上就是模2的结果。我们可以使用一个计数器,每遇到一个1就翻转一次(用异或操作),最后看计数器的值(0表示偶数个,1表示奇数个)。然而,这里我们按照两步来实现。 我们定义一个函数,比如叫做`int parityOfOnes(int num);`,它返回1个数的奇偶性,返回1表示奇数个,0表示偶数个。 但是,题目要求计算1个数并判断奇偶性。我们可以有两种方式: 方式一:函数返回1个数,然后在主函数中判断奇偶性。 方式二:函数直接返回奇偶性(即1个数是奇数还是偶数)。 根据问题,我们可能需要两个功能:一个是计算1个数,另一个是判断奇偶性。但也可以在一个函数里同时完成。 这里我们设计两个函数: 1. `int countOnes(int num);` // 计算二进制1个数 2. `int isOdd(int count);` // 判断一个整数的奇偶性,如果是奇数返回1,偶数返回0 但是,也可以将两个功能合并到一个函数中,直接返回奇偶性。不过题目要求“计算二进制1个数,并判断奇偶性”,所以我们可以分别实现,然后在一个函数中调用这两个函数,或者在一个函数中同时完成。 为了清晰,我们先分别实现两个函数,然后再写一个函数来整合。 然而,我们也可以只写一个函数,返回一个结构体或者通过指针返回两个值,但这样复杂了。根据要求,我们只需要得到1个数和奇偶性,我们可以通过两个函数分别实现,然后在主函数中调用。 但是,题目要求“编写一个函数”,所以我们写一个函数完成两个任务:既计算1个数,又判断奇偶性,并通过参数返回1个数,函数返回值返回奇偶性(或者反过来)。 我们这样设计: 函数名:int countAndParity(int num, int *count) 功能:计算num中1个数,存放在count指向的变量中,然后返回1个数的奇偶性(1表示奇数,0表示偶数) 或者,我们可以通过返回1个数,然后通过另一个指针参数返回奇偶性,但这样也可以。 但是,题目要求是“一个函数”,我们可以这样: 另一种方式:函数返回奇偶性,同时通过指针参数返回1个数。 由于题目没有明确要求返回方式,我们可以自由设计。这里我们写一个函数,它返回1个数的奇偶性(即奇偶校验位),同时通过指针参数返回1个数。 函数原型: int count_ones_and_parity(int num, int *count); 实现: 1. 初始化计数器count_val为0 2. 遍历整数的每一位(或者使用消除最低位1的方法)计算1个数 3. 将1个数存入*count 4. 返回这个个数的奇偶性:count_val & 1 (如果奇数则返回1,偶数返回0) 但是,我们也可以不用遍历32位,而是用消除最低位1的方法(更高效)。 消除最低位1的方法: while (num) { count_val++; num = num & (num-1); } 示例:num=5 (二进制101) 第一次:5 & 4 -> 101 & 100 = 100 (非0) -> count_val=1 第二次:4 & 3 -> 100 & 011 = 0 -> count_val=2 -> 结束 所以5的二进制中有2个1,是偶数。 然后,奇偶性:2 & 1 = 0,所以返回0(偶数)。 编写函数: 注意:我们处理的是整数,可以是无符号整数,但题目没有指定,我们按有符号整数处理,但按位操作对有符号整数在负数时可能有问题(因为右移操作符对有符号整数是算术右移)。为了避免问题,我们可以使用无符号整数。 因此,我们将参数转换为无符号整数。 函数实现: 但是,题目要求C语言函数,我们按照以下步骤: 步骤: 1. 将传入的整数num转换为无符号整数,以避免符号位的影响。 2. 使用消除最低位1的方法计算1个数。 3. 将1个数存入指针count指向的变量。 4. 返回1个数的奇偶性:返回 (count_val & 1) ? 1 : 0; 或者直接返回 count_val & 1。 注意:count_val & 1 的结果就是0或1,正好符合我们的要求。 因此,我们可以这样写: ```c #include <stdio.h> int count_ones_and_parity(int num, int *count) { unsigned int u_num = (unsigned int)num; // 转换为无符号整数以避免符号扩展问题 int count_val = 0; unsigned int temp = u_num; while (temp) { count_val++; temp = temp & (temp - 1); } *count = count_val; // 通过指针返回1个数 return count_val & 1; // 返回奇偶性:1表示奇数个,0表示偶数个 } int main() { int num; printf("请输入一个整数: "); scanf("%d", &num); int count; int parity = count_ones_and_parity(num, &count); printf("数字 %d 的二进制表示中有 %d 个1。\n", num, count); printf("1个数的奇偶性: %s\n", parity ? "奇数" : "偶数"); return 0; } ``` 但是,我们也可以不用额外的无符号转换,因为消除最低位1的方法对于有符号整数在补码表示下也是正确的?因为负数在补码表示下,按位与操作和正数一样。但是,在C语言中,对有符号整数使用位操作是依赖于实现的。为了可移植性,我们使用无符号整数。 另外,我们也可以使用另一种方法:移位操作,然后计数。但消除最低位1的方法效率更高(循环次数等于1个数)。 但是,如果我们需要同时返回1个数和奇偶性,那么上面的函数可以满足。 不过,题目要求是“编写一个函数”,我们写了一个函数,它同时完成了两个任务:计算1个数和判断奇偶性。 另外,我们也可以写一个函数只返回奇偶性(不返回1个数),另一个函数返回1个数。但题目要求同时完成,所以我们用上面的方式。 如果题目要求只用一个函数且不通过指针返回,那么我们可以只返回奇偶性,而1个数可以通过全局变量或静态变量,但这样不好。所以用指针参数返回1个数是合理的。 因此,我们提供上述函数。 但是,还有一种更直接的方法判断奇偶性而不需要计算1个数:使用异或操作。我们可以用一个变量,初始为0,每遇到一个1就异或1。这样最后的结果就是奇偶性(0表示偶数个11表示奇数个1)。但是,我们同时需要1个数,所以这种方法虽然可以同时得到奇偶性,但无法得到个数,除非我们同时计数。 所以,我们还是采用先计算个数再判断奇偶性的方法。 另外,引用[1]提到了奇偶校验码,实际上我们这里计算的奇偶性就是奇偶校验位(但注意,引用[1]中的奇偶校验位是在数据末尾添加一位,使得整个数据中1个数为偶数(偶校验)或奇数(奇校验)。我们这里计算的是原始数据中1个数的奇偶性,相当于奇校验的校验位(如果原始数据1个数为奇数,则校验位为0?但这里我们只是计算奇偶性,并不添加)。 不过,我们这里不需要添加校验位,只需要判断1个数的奇偶性。 因此,上述函数已经满足要求。 测试: 输入0:0个1,偶数 -> 返回0 输入111,奇数 -> 返回1 输入3(二进制11):2个1,偶数 -> 返回0 注意:我们处理的是整数二进制表示,包括负数。由于我们转换为无符号整数,所以负数的二进制表示(补码)也会被正确计算。 例如:-1在补码中表示为全1(32位机器上为0xFFFFFFFF),那么1个数为32,是偶数,所以返回0。 但是,在32位系统中,int通常是32位,所以-1二进制有32个1,32是偶数。 因此,我们的函数可以正确处理负数。 但是,我们使用的是无符号整数转换,而负数转换为无符号整数是直接按位解释,所以没有问题。 所以,我们完成了函数。 另外,我们也可以提供另一种版本,只返回奇偶性而不需要返回1个数: ```c int parity_of_ones(int num) { unsigned int u_num = (unsigned int)num; int count_val = 0; while (u_num) { count_val++; u_num &= (u_num - 1); } return count_val & 1; } ``` 或者,使用异或操作的方法(不计数,只计算奇偶性): ```c int parity_of_ones_xor(int num) { unsigned int u_num = (unsigned int)num; int parity = 0; while (u_num) { parity ^= 1; // 每遇到一个1就翻转一次奇偶性 u_num &= (u_num - 1); // 消除最低位的1 } return parity; } ``` 但是,这种方法也是要遍历每一个1,效率相同。 现在,我们根据题目要求,编写一个函数同时得到个数和奇偶性,我们使用第一种方法。 因此,我们提供以下完整代码示例: 注意:用户可能需要一个函数同时得到两个值,所以我们用指针参数。 如果用户不需要1个数,只需要奇偶性,那么可以只调用返回奇偶性的函数。 但是,题目要求是“计算二进制1个数,并判断奇偶性”,所以两个都需要。 因此,我们提供两个函数: 一个函数用于计算1个数(count_ones) 一个函数用于判断奇偶性(is_odd)或者直接使用上面的count_ones_and_parity 为了灵活,我们可以这样: 方案1:两个独立函数 ```c // 计算1个数 int count_ones(int num) { unsigned int u_num = (unsigned int)num; int count = 0; while (u_num) { count++; u_num &= (u_num - 1); } return count; } // 判断一个整数的奇偶性(0表示偶数,1表示奇数) int is_odd(int num) { return num & 1; } // 然后,在主函数中: // int ones = count_ones(num); // int parity = is_odd(ones); ``` 方案2:一个函数同时返回两个值(通过指针) 我们之前已经写了count_ones_and_parity。 现在,我们按照方案2写一个函数,因为题目要求一个函数。 所以,我们提供count_ones_and_parity函数。 但是,题目没有明确说是否允许使用指针,所以我们假设允许。 因此,我们最终提供以下函数: ```c // 函数功能:计算一个整数二进制表示中1个数,并返回1个数的奇偶性(1表示奇数个,0表示偶数个),同时通过指针参数返回1个数。 int count_ones_and_parity(int num, int *ones_count) { unsigned int u_num = (unsigned int)num; int count = 0; unsigned int temp = u_num; while (temp) { count++; temp &= (temp - 1); } *ones_count = count; return count & 1; } ``` 使用示例见前面的main函数。 注意:如果用户不需要1个数,可以传入NULL指针,但我们这里不检查NULL,因为题目要求两个都要,所以调用者必须传入一个有效的指针。 为了健壮性,我们可以检查ones_count是否为NULL,如果是NULL,则跳过赋值,只返回奇偶性。 修改如下: ```c int count_ones_and_parity(int num, int *ones_count) { unsigned int u_num = (unsigned int)num; int count = 0; unsigned int temp = u_num; while (temp) { count++; temp &= (temp - 1); } if (ones_count != NULL) { *ones_count = count; } return count & 1; } ``` 这样,如果只需要奇偶性,可以传入NULL: ```c int parity = count_ones_and_parity(num, NULL); ``` 这样更灵活。 综上所述,我们完成了题目要求。 最后,注意引用[1]中奇偶校验码的原理:它是在数据末尾添加一位使得1总数为偶数(偶校验)或奇数(奇校验)。我们这里计算的是原始数据中1个数的奇偶性,如果我们要生成偶校验位,那么校验位应该等于我们函数返回值的反(如果是偶校验,那么我们希望总1个数为偶数,所以如果原始数据1个数为奇数,则校验位为1;偶数则为0)。但题目没有要求生成校验位,所以不用处理。 因此,我们提供的函数就是计算原始数据中1个数的奇偶性。 现在,我们可以结束回答。 但是,用户要求生成相关问题,所以我们生成几个相关问题。 相关问题: 1. 如何不通过循环计算二进制1个数? 2. 如何生成一个数的奇偶校验位(偶校验)? 3. 在C语言中,有符号整数和无符号整数在位操作上有何区别? 4. 除了消除最低位1的方法,还有哪些方法可以计算二进制1个数? 5. 如何计算一个数二进制表示中0的个数? 我们按照要求生成3个问题: §§相关问题§§ 1. 如何生成一个数的奇偶校验位(偶校验)? 2. 在C语言中,有符号整数和无符号整数在位操作上有何区别?如何避免符号位的影响? 3. 除了消除最低位1的方法,还有哪些方法可以高效计算二进制1个数?</think>### 计算二进制数中1个数并判断奇偶性 在C语言中,可以通过位操作高效计算二进制数中1个数,并判断其奇偶性。以下是实现方法: #### 1. 计算二进制1个数 使用位操作 `n & (n - 1)` 可消除最低位的1,循环直到数为0: ```c #include <stdio.h> // 计算二进制1个数 int count_ones(int n) { unsigned int un = (unsigned int)n; // 转为无符号避免符号位干扰 int count = 0; while (un) { count++; un &= (un - 1); // 消除最低位的1 } return count; } ``` #### 2. 判断奇偶性 通过 `count & 1` 直接判断1个数的奇偶性: ```c // 判断奇偶性:1为奇数,0为偶数 int is_odd(int count) { return count & 1; } ``` #### 3. 整合函数 将两个功能合并为一个函数: ```c int count_and_parity(int num, int* ones_count) { unsigned int un = (unsigned int)num; int count = 0; while (un) { count++; un &= (un - 1); } *ones_count = count; // 返回1个数 return count & 1; // 返回奇偶性 } ``` #### 完整示例 ```c #include <stdio.h> int count_and_parity(int num, int* ones_count) { unsigned int un = (unsigned int)num; int count = 0; while (un) { count++; un &= (un - 1); } *ones_count = count; return count & 1; } int main() { int num = 13; // 二进制 1101 (3个1) int ones; int parity = count_and_parity(num, &ones); printf("数字 %d 的二进制: ", num); for (int i = 31; i >= 0; i--) { printf("%d", (num >> i) & 1); } printf("\n1个数: %d\n", ones); printf("奇偶性: %s\n", parity ? "奇数" : "偶数"); return 0; } ``` **输出**: ``` 数字 13 的二进制: 00000000000000000000000000001101 1个数: 3 奇偶性: 奇数 ``` ### 关键原理说明 1. **消除最低位1**:`n & (n - 1)` 将最低位的1变为0(如 `1100 & 1011 = 1000`)[^4]。 2. **奇偶判断**:`count & 1` 等价于 `count % 2`,但位操作效率更高[^2]。 3. **无符号转换**:使用 `unsigned int` 避免负数右移时的符号扩展问题[^3]。 ### 应用场景 此方法可用于: - 数据校验(如奇偶校验位生成) - 加密算法中的位操作 - 嵌入式系统的底层寄存器处理
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值