求二进制中1的个数

本文整理了多种用于计算整数二进制位中1的个数的方法,包括模2操作、与1操作、分支结构、查表法以及平行算法和完美法等。详细解释了每种方法的原理和应用,特别强调了32位整数场景下的优化策略。

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

这道题在《编程之美》和《剑指offer》上都有,本文加以整理。(以32位为例)

首先,都知道用n&(n-1)把整数n最右边的1变为0,于是就有个“目前”个人认为最好的方法

int Count(int n)
{
	int num =0;
	while(n)
	{
		n &= (n-1);//将整数最右边的1变为0,有多少1就做多少次这种操作。
		num++;
	}
	return num;
}

注意到上面的n是有符号的,也就是说上面的方法对无符号同样适用。如果n是无符号数,按照书上的方法,还有:

1、模2的结果是否为1,当末位是1时,num++,然后除以2;做同样地操作,一直到n为0;

2、用与1作"&"操作代替mod操作,右移代替除法操作;

给出2的代码如下:

int Count(unsigned int n )
{
	int num=0;
	while(n)
	{
		num += n & 0x01;
		n>>1;
	}
	return num;
}
n为有符号数时,如果最高位1(即n为负数),右移操作最终会出现死循环(n会变为0xFFFFFFFF)。

当然,《编程之美》上还给出了分支操作和查表法,由于这里n为32位,显然,这两种方法都不太适用,如果n只有4位(假设为无符号的二进制)

/*n为4位无符号的二进制数,计算其二进制位中1的个数
*         以下方法知道其思想就可以了     */

//使用分支结构
int Count(unsigned int n)
{
	int num = 0;
	switch (n)
	{
	case 0x0:
		num = 0;
		break;
	case 0x1:
	case 0x2:
	case 0x4:
	case 0x8:
		num = 1;
		break;
	case 0x3:
	case 0x5:
	case 0x6:
	case 0x9:
	case 0xa:
	case 0xc:
		num = 2;
		break;
	case 0x7:
	case 0xb:
	case 0xd:
	case 0xe:
		num = 3;
		break;
	case 0xf:
		num = 4;
		break;
	}
	return num;
}

//查表法,将“1”的个数存储在数组中,4位无符号数可以表示0~15
int countTable[16]={0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4};
int Count(unsigned int n)
{
	return countTable[n];
}
重点讲以下两种:平行算法和完美法

平行算法

int Count(unsigned int n) 
{ 
    n = (n &0x55555555) + ((n >>1) &0x55555555) ;
    n = (n &0x33333333) + ((n >>2) &0x33333333) ; 
    n = (n &0x0f0f0f0f) + ((n >>4) &0x0f0f0f0f) ; 
    n = (n &0x00ff00ff) + ((n >>8) &0x00ff00ff) ; 
    n = (n &0x0000ffff) + ((n >>16) &0x0000ffff) ; 

    return n ; 
}

速度不一定最快,但是想法绝对巧妙。 说一下其中奥妙,其实很简单,先将n写成二进制形式,然后相邻位相加,重复这个过程,直到只剩下一位。以217(11011001)为例,有图有真相,下面的图足以说明一切了。217的二进制表示中有5个1

1  1  0  1   1   0   0  1

  2     1      1      1

     3             2

            5


完美法

int Count2(unsigned int n) 
{
    unsigned int tmp = n - ((n >>1) &033333333333) - ((n >>2) &011111111111);
    return ((tmp + (tmp >>3)) &030707070707) %63;
}


第一行代码的作用

先说明一点,以0开头的是8进制数,以0x开头的是十六进制数,上面代码中使用了三个8进制数

将n的二进制表示写出来,然后每3bit分成一组,求出每一组中1的个数,再表示成二进制的形式。比如n = 50,其二进制表示为110010,分组后是110和010,这两组中1的个数本别是2和3。2对应010,3对应011,所以第一行代码结束后,tmp = 010011,具体是怎么实现的呢?由于每组3bit,所以这3bit对应的十进制数都能表示为2^2 * a + 2^1 * b + c的形式,也就是4a + 2b + c的形式,这里a,b,c的值为0或1,如果为0表示对应的二进制位上是0,如果为1表示对应的二进制位上是1,所以a + b + c的值也就是4a + 2b + c的二进制数中1的个数了。举个例子,十进制数6(0110)= 4 * 1 + 2 * 1 + 0,这里a = 1, b = 1, c = 0, a + b + c = 2,所以6的二进制表示中有两个1。现在的问题是,如何得到a + b + c呢?注意位运算中,右移一位相当于除2,就利用这个性质!

4a + 2b + c 右移一位等于2a + b

4a + 2b + c 右移量位等于a

然后做减法

4a + 2b + c –(2a + b) – a = a + b + c,这就是第一行代码所作的事,明白了吧。

第二行代码的作用

在第一行的基础上,将tmp中相邻的两组中1的个数累加,由于累加到过程中有些组被重复加了一次,所以要舍弃这些多加的部分,这就是&030707070707的作用,又由于最终结果可能大于63,所以要取模。需要注意的是,经过第一行代码后,从右侧起,每相邻的3bit只有四种可能,即000, 001, 010, 011,为啥呢?因为每3bit中1的个数最多为3。所以下面的加法中不存在进位的问题,因为3 + 3 = 6,不足8,不会产生进位。

tmp + (tmp >> 3)-这句就是是相邻组相加,注意会产生重复相加的部分,比如tmp = 659 = 001 010 010 011时,tmp >> 3 = 000 001 010 010,相加得

001 010 010 011

000 001 010 010

---------------------

001 011 100 101

011 + 101 = 3 + 5 = 8。注意,659只是个中间变量,这个结果不代表659这个数的二进制形式中有8个1。注意我们想要的只是第二组和最后一组(绿色部分),而第一组和第三组(红色部分)属于重复相加的部分,要消除掉,这就是&030707070707所完成的任务(每隔三位删除三位),最后为什么还要%63呢?因为上面相当于每次计算相连的6bit中1的个数,最多是111111 = 77(八进制)= 63(十进制),所以最后要对63取模

注:后两种方法见  点击打开链接


内容概要:本文档详细介绍了基于MATLAB实现多目标差分进化(MODE)算法进行无人机三维路径规划的项目实例。项目旨在提升无人机在复杂三维环境中路径规划的精度、实时性、多目标协调处理能力、障碍物避让能力和路径平滑性。通过引入多目标差分进化算法,项目解决了传统路径规划算法在动态环境和多目标优化中的不足,实现了路径长度、飞行安全距离、能耗等多个目标的协调优化。文档涵盖了环境建模、路径编码、多目标优化策略、障碍物检测与避让、路径平滑处理等关键技术模块,并提供了部分MATLAB代码示例。 适合人群:具备一定编程基础,对无人机路径规划和多目标优化算法感兴趣的科研人员、工程师和研究生。 使用场景及目标:①适用于无人机在军事侦察、环境监测、灾害救援、物流运输、城市管理等领域的三维路径规划;②通过多目标差分进化算法,优化路径长度、飞行安全距离、能耗等多目标,提升无人机任务执行效率和安全性;③解决动态环境变化、实时路径调整和复杂障碍物避让等问题。 其他说明:项目采用模块化设计,便于集成不同的优化目标和动态环境因素,支持后续算法升级与功能扩展。通过系统实现和仿真实验验证,项目不仅提升了理论研究的实用价值,还为无人机智能自主飞行提供了技术基础。文档提供了详细的代码示例,有助于读者深入理解和实践该项目。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值