二进制中 ‘1’ 的个数

对于一个字节是4byte(32bit)的变量,求其中二进制表示中 1 的个数,要尽可能的高效率! image.png

分析与解法

大多数的读者都会有这样的反应:这个题目也太简单了!解法也相对单一,不会出现有任何难点,分析起来也很简单。那么这个题目有什么意义呢!但事实上,对于一个程序而言,有些应用,对运算速率要求尽可能“高”!

解法【一】:

我们最常用的方法就是用一个循环去控制求余和整除:如我们可以拿 0x11223344(0x是16进制数):二进制表示为:00010001 00100010 00110011 01000100
除一个2 就会减少一个0,取模的过程中,余数不为0,则证明这个位置的二进制数是1 以00010001 00100010 00110011 01000100 为例 第一次除2 商为00010001 00100010 00110011 0100010 余数为0 第二次除2 商为00010001 00100010 00110011 010001 余数为0 第三次除2 商为00010001 00100010 00110011 01000 余数为1 ···· ···· ···· 第32次除2 商为0,余数为0, 退出循环 代码展示:

#include<stdio.h>
int main()
{
	int a = 0x1122334;
	int i = 0;
	int b = 0;
	int count = 0;
	//对这个数进行32次(32字节)循环二进制数除2和%2
	for (i = 0; i < 32; i++)
	{
		b = a % 2;
		a = a / 2;
		//统计1的个数
		if (b == 1)
		{
			count++;
		}
	}
	printf("%d", count);
	return 0;
}


解法【二】:

前面的代码看起来简单,但是它的执行时间是相对很长的。我们知道向右移位 和 & 操作符也可以达到相除和求1的功能。 我们拿 0x00112233 为例:在向右移的过程中,我们会丢弃最右边的一个数字,因此我们需要先去判断这个数字是0 还是1,所以我们先用按位与(&)操作符: 0x00112233 的二进制代码为 00000000 00010001 00100010 00110011 ::: hljs-center 00000000 00010001 00100010 00110011
00000000 00000000 00000000 00000001 & 结果为 00000000 00000000 00000000 00000001(结果为1) ::: 然后我们把 00000000 00010001 00100010 00110011 则变为了 0 00000000 00010001 00100010 0011001 (最左边补0,最右边的数丢弃)

代码展示:

#include<stdio.h>
int main()
{
	int a = 0x00112233;
	int num = 0;
	while (a)
	{
		num += a & 0x00000001;
		a >>= 1;
	}
	printf("%d", num);
	return 0;
}

解法【三】:

位操作比除,余的效率高很多。但是,即使采用位操作,时间复杂度仍然为O(log2V)(2是底,V是指数,(log2V)为二进制数的位数。那么还有什么办法可以降低时间复杂度呢!因为我们只是需要求‘1’的个数,那么我们只关注二进制数中的1,复杂度相应就能降低了吧! 我们用 a = 0x80000000 来举例: 二进制表示为:10000000 00000000 00000000 00000000 那 a - 1 的二进制表示为:01111111 11111111 11111111 11111111 并且用 a & a - 1 = 0;则a二进制只有 1 个 1 。 代码展示:

#include<stdio.h>
int main()
{
	int a = 0x80000000; 
	int num = 0;
	while (a)
	{
		a = a & (a - 1);
		num++;
	}
	printf("%d", num);
	return 0;
}

这个算法的时间复杂度为O(m),其中的 m 是a中1的个数。