一道简单的算法题
題目:統計給定數字中,值為1的二進制位的數量。如果是數組呢?
解法1:遍歷算法
int getBitCount(unsigned int num) {int count = 0;while(num) {if(num & 0x01)count++;num = num >> 1;}return count; }第一種想法比較簡單,從最后一位開始,比較是否為1,如果為1,就計數器加一。循環次數固定,32次。但是這種方法有一個地方需要注意,那就形參必須為unsigned int。否則,如果num為負數時,此時右移為了保證移位后還是負數,最高位會一直置為1,那將陷入死循環。
解法2:遍歷算法(改進)
int getBitCount2(unsigned int num) {int count = 0;while(num) {count++;num = num & (num - 1);}return count; }相比較第一種算法,我們不必每次判斷一位,而是通過num & (num-1)來判斷。每次&之后,可以把num最低位的1置為0,那么循環的次數,也就降低到了所含1的個數。
解法3:查表法
static const unsigned char bitsinbyte[256] = {//0000 0000 - 0000 00010,1,//0000 0010 - 0000 00111,2,//0000 0100 - 0000 01111,2,2,3,//0000 1000 - 0000 11111,2,2,3,2,3,3,4,//0001 0000 - 0001 11111,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,//0010 0000 - 0011 11111,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,//0100 0000 - 0111 11111,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,//1000 0000 - 1111 11111,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8 }; int getBitCount3(unsigned int num) {unsigned char n1 = num;unsigned char n2 = num >> 8;unsigned char n3 = num >> 16;unsigned char n4 = num >> 24;return bitsinbyte[n1] + bitsinbyte[n2] +bitsinbyte[n3] + bitsinbyte[n4]; }通過定義一個bitsinbyte[256]字節數組,里面存放0000 0000 - 1111 1111不同數字的1的個數。然后把需要統計的數字劃分為四段,然后分部計算。
解法4:variable-precision SWAR算法
unsigned int getBitCount4(unsigned int num) {num = (num & 0x55555555) + ((num >> 1) & 0x55555555);num = (num & 0x33333333) + ((num >> 2) & 0x33333333);num = (num & 0x0F0F0F0F) + ((num >> 4) & 0x0F0F0F0F);num = (num * 0x01010101 >> 24);return num; }這種算法也常被稱為漢明重量(Hamming Weight),通過一系列的位移和位運算操作,可以在常數時間內計算多個字節的漢明重量,而且不需要使用額外的內存。接下來分析以下這個算法。
為了方便描述,我們假定一個字節0xD8 ->(二進制) 0B11011000從后往前,依次為1到8位,第一位為0,第八位為1。
-
step1:首先我們可以很容易的知道,0x55555555對應的二進制的數為0B 01010101 01010101 01010101 01010101,而第一步運算相當于,把num奇偶位的數字進行相加。并且存放在了奇數位,相加如有進位則放在偶數位。
-
step2:0x33333333對應的二進制的數為0B 00110011 00110011 00110011 00110011,把num的奇數位,與下一個奇數位相加(第一位加第三位,第五位加第七位),把num的偶數位,與下一個偶數位相加(第二位加第四位,第六位加第八位)。如有進位,則保存到第三位,或者第七位。
-
step3:0x0F0F0F0F對應的二進制的數為0B 00001111 00001111 00001111 00001111,把num的每個字節中,前四位,與后四位相加。此時,每個字節中所含1的個數,都集中到了前四位。此時可以用0x0m0n0i0j來表示這個數,其中m,n,i,j代表之前num每個字節所含1的個數。
-
step4:也是最神奇的一步,通過這一步,把m,n,i,j這四個數相加。得到最終的個數。在這一步,我們不需要把0x01010101化為二進制。而是直接帶入運算。通過下面的計算式,我們可以看出相乘,然后右移24位,剛好就是我們所要的結果。神奇~
magic.png
運算速度
| 遍歷 | 0 | 0 | 1 | 2 | 26 | 255 | 2700 | 29447 |
| 遍歷(改進) | 0 | 0 | 0 | 1 | 7 | 74 | 739 | 8046 |
| 查表 | 0 | 0 | 0 | 0 | 2 | 21 | 202 | 2166 |
| SWAR | 0 | 0 | 0 | 0 | 2 | 19 | 190 | 1876 |
以上是模擬不同的數據級別,運行測試的結果。
參考資料:
- http://stackoverflow.com/questions/109023/how-to-count-the-number-of-set-bits-in-a-32-bit-integer
- <<劍指offer>> 第十題
- <<redis設計與實現>> 第22章,BITCOUNT實現
- 測試代碼
總結
- 上一篇: Deep Learning 中文翻译
- 下一篇: 分享成为高效程序员的7个重要习惯