【编程之美学习笔记】2.1求二进制数中1的个数

问题:对于一个字节(8bit)的无符号整型变量,求其二进制表示中“1”的个数,要求算法的执行效率尽可能高。

解法一:除、余操作

我们知道,对于二进制操作,除以一个2,原来的数字将会减少一个0,如果除的过程中有余,那么就表示当前位置有一个1,所以可通过相除和判断余数的值来分析。

【时间复杂度O(log2v),log2v为二进制数的位数,空间复杂度O(1)】

1 int Count(int v){
2     int num = 0;
3     while(v){
4         if(v % 2 == 1) num++;
5         v = v/ 2;
6     }
7     return num;
8 }
View Code

 解法二:位操作

我们也知道,向右移位操作同样可以达到相除的目的,对于如何判断移位之后是否有1存在,可以在右移位过程中,将这个八位数字与00000001进行“与”操作,如果结果为1,则表示当前八位数的最后一位为1,否则为0.

【时间复杂度O(log2v),空间复杂度O(1)】

1 int Count(int v){
2     int num = 0;
3     while(v){
4         num += v & 0x01;
5         v >>= 1;
6     }
7     return num;
8 }
View Code

 解法三:高效位操作

我们发现,前面两种操作,对于一些数,比如10000000,就会浪费大量时间在无用的0上,那么能不能让算法的复杂度只与“1”的个数有关呢?

通过观察发现有个规律:使 v = v & (v - 1),可以去除数 v 的二进制中最后一个1,

比如 01000010 & (01000010 - 00000001)= 01000000 & 01000001 = 01000000

      01000000 & (01000000 - 00000001) = 01000000 & 00111111 = 0

【时间复杂度O(M),即二进制中“1”的个数,空间复杂度O(1)】

1 int Count(int v){
2     int num = 0;
3     while(v){
4         v &= (v-1);
5         num++;
6     }
7     return num;
8 }
View Code

 解法四:分支操作

罗列出0~255的情况,并使用分支操作得到答案。但是这个方法看似直接,执行效率却可能低于上述解法,因为分支语句的执行情况要看具体字节的值,例如,若v = 0,在第一个case就能得出答案,但是若v = 255,则要比较255次!因此该解法并不可取,但提供了个很好的思路,即空间换时间的方法。

 1 int Count(int v){
 2     int num = 0;
 3     switch(v){
 4         case 0x0: num = 0; break;
 5         case 0x1:
 6         case 0x2:
 7         case 0x4:
 8         case 0x8:
 9         case 0x10:
10         case 0x20:
11         case 0x40:
12         case 0x80: num = 1; break;
13         case 0x3:
14         case 0x6:
15         case 0xc:
16         case 0x18:
17         case 0x30:
18         case 0x60:
19         case 0xc0: num = 2; break;
20         //...
21     }
22     return num;
23 }*
View Code

 解法五:查表法

典型的空间换时间的算法,应题目要求,只要把0~255中“1”的个数直接存储在数组中即可

【时间复杂度O(1),空间复杂度O(N)】

 1 int CountTable[256]={
 2         0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4,
 3         1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
 4         1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
 5         2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
 6         1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
 7         2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
 8         2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
 9         3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
10         1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
11         2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
12         2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
13         3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
14         2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
15         3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
16         3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
17         4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8,
18 };
19 int Count(int v){
20     //check parameter
21     return CountTable[v];
22 }
View Code

 网上其他解法:以32位整型为例

解法六:归并法

相邻两两相加,然后四个四个相加,以此类推。最后就能得出各位之和。

 1 int Count(int v){
 2     int num = v;
 3     int a = 0x55555555;//01010101010101010101010101010101
 4                     //用于相邻两位相加
 5     int b = 0x33333333;//00110011001100110011001100110011
 6                     //用于相邻四位相加
 7     int c = 0x0f0f0f0f;//00001111000011110000111100001111
 8     int d = 0x00ff00ff;//00000000111111110000000011111111
 9     int e = 0x0000ffff;//00000000000000001111111111111111
10     num = (num& a) + ((num >> 1)& a);
11     num = (num& b) + ((num >> 2)& b);
12     num = (num& c) + ((num >> 4)& c);
13     num = (num& d) + ((num >> 8)& d);
14     num = (num& e) + ((num >> 16)& e);
15     return num;
16 }
View Code

解法七:HAKMEN算法

首先将二进制各位三个一组,求出每组中1的个数,然后相邻两组归并,得到六个一组的1的个数,最后巧妙的用除63取余得到结果。

因为2^6 = 64,所以v_0 + v_1 * 64 + v_2 * 64 * 64 = v_0 + v_1 + v_2(mod 63),等号表示同余。(表示现在还不是很理解orz)

 1 int Count(unsigned v){
 2     unsigned n;
 3     n = (v >> 1)& 033333333333;
 4     v = v - n;
 5     n = (n >> 1)& 033333333333;
 6     v = v - n;
 7     v = (v + (v >> 3))& 030707070707;
 8     v = v % 63;
 9     return v;
10 }
View Code

 扩展:由解法三的规律可以轻松判断一个数是否是2的幂。

1 int powof2(int v){
2     return (v & (v-1)) == 0;
3 }
View Code

 扩展问题:

1.如果变量是32位的DWORD,你会使用上述的哪一个算法,或者改进哪一个算法?

解法三,六,七。(解法五不合适,因为要求的数组太大了:2^32*sizeof(int))

2.给定两个正整数(二进制形式表示)A和B,问把A变为B需要改变多少位(bit)?也就是说,整数A和B的二进制表示中有多少位是不同的?

答案即A^B 中 1 的个数。

原文地址:https://www.cnblogs.com/GraceSkyer/p/5836718.html