算法:統計整數的二進制表達式中的bit位爲1的位數
普通算法
public int bitCount(int num) {
int count = 0;
do {
if ((num & 1) == 1) {
count++;
}
num>>=1;
} while (num > 0);
return count;
}
應該是最先想到的算法了,從最低位開始,一位一位地統計是否爲1,時間複雜度爲O(n)
,n爲總bit數。
優化算法
public int countBit2(int num) {
int count = 0;
while (num > 0) {
num = num & (num - 1);
count++;
}
return count;
}
這個算法乍看很懵逼,但是仔細琢磨一下也能發現原理:n-1
後,n的最低位的1被消除了,然後與n位與,n變爲最低位1置爲0後的新整數,如:
0b101100 減一 0b101011 最低位的1消除,0b101100 & 0b101011 = 0b101000
如此循環多少次就有多少個1,時間複雜度也是O(n)
,但是這個n表示bit位爲1的個數,總體是要比上一個優一點的。
當我們以爲這已經是最優的算法了,事實卻並非如此
Integer.bitCount
public static int bitCount(int i) {
// HD, Figure 5-2
i = i - ((i >>> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
i = (i + (i >>> 4)) & 0x0f0f0f0f;
i = i + (i >>> 8);
i = i + (i >>> 16);
return i & 0x3f;
}
最後,其實java的Integer類已經提供了一個方法來統計bit位(無符號右移,可以統計負數的),乍看之下,WTF?
原理:想象一下,當一列的1擺在我們人腦的面前,我們會怎麼數?一個一個數,第一個的算法的原理。或者兩個兩個地數?本方法就是如此實現的。如下圖:
二進制 十進制
1 0 1 1 1 1 1 1 1 1 10 11 11 11 11
01 10 10 10 10 1 2 2 2 2
\ / \ / \/ \/
01 0100 0100 1 4 4
\ / \ /
01 1000 1 8
\ / \ /
1001 9
767的二進制中的1的位數計算過程
每兩位bit爲一組,分別統計有幾個1,然後把結果存到這兩個bit位上,如:11
有2個1,結果爲10
,10
替代11
的存儲到原位置。然後進行加法計算,把所有的結果加起來。加的過程中呢又可以兩兩相加,減少計算流程。
兩個bit計算1的數量:0b11: 0b01 + 0b01 = 0b10 = 2
, 0b10: 0b01 + 0b00 = 0b01 = 1
,這樣就清楚了。
算法實現如下:
- 首先整數i抹除左一位:
i & 0x55555555
,然後錯位相加。(i >>> 1) & 0x55555555
表示:左位移到右邊,再把左位抹除,這樣就可以計算兩個bit位上1的個數了:0b1011=>0b0001 + 0b0101 = 0b0110
左兩位有1個1,右兩位有2個1。 - 這時
i
中存儲了每兩位的統計結果,可以進行兩兩相加,最後求和。
過程:
0x55555555 0b01010101010101010101010101010101
0x33333333 0b00110011001100110011001100110011
0x0f0f0f0f 0b00001111000011110000111100001111
0x00ff00ff 0b00000000111111110000000011111111
0x0000ffff 0b00000000000000001111111111111111
0x3f 0b00111111
0b11 11 11 11 11 (i & 0x55555555) + ((i >>> 1) & 0x55555555) = 0b0101010101 + 0b0101010101 = 0b1010101010
0b10 10 10 10 10 (i & 0x33333333) + ((i >>> 2) & 0x33333333) = 0b1000100010 + 0b00100010 = 0b1001000100
0b10 01 00 01 00 (i & 0x0f0f0f0f) + ((i >>> 4) & 0x0f0f0f0f) = 0b1000000100 + 0b0100 = 0b1000001000
0b10 00 00 10 00 (i & 0x00ff00ff) + ((i >>> 8) & 0x00ff00ff) = 0b1000 + 0b10 = 0b1010
0b00 00 00 10 10 (i & 0x0000ffff) + ((i >>> 16) & 0x0000ffff) = 0b1010 + 0 = 0b1010
dec 10
算法原型:
public static int bitCount(int i) {
i = (i & 0x55555555) + ((i >>> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
i = (i & 0x0f0f0f0f) + ((i >>> 4) & 0x0f0f0f0f);
i = (i & 0x00ff00ff) + ((i >>> 8) & 0x00ff00ff);
i = (i & 0x0000ffff) + ((i >>> 16) & 0x0000ffff);
return i;
}
時間複雜度O(1),可以,很ok了!但是寫文章都要潤色下的,別說算法了,然後優化過後的就是Integer中的實現了。
優化:
- 第一步:兩個bit計算1的數量:
0b11: 0b01 + 0b01 = 0b10 = 2
,0b10: 0b00 + 0b01 = 0b01 = 1
。研究發現:2=0b11-0b1
,1=0b10-0b1
,可以減少一次位於計算:i = i - ((i >>> 1) & 0x55555555)
- 第二步:暫時沒有好的優化方法
- 第三步:實際是計算每個byte中的1的數量,最多8(0b1000)個,佔4bit,可以最後進行位與運算消位,減少一次
&
運算:i = (i + (i >>> 4)) & 0x0f0f0f0f
- 第四,五步:同上理由,可以最後消位。但是由於int最多32(0b100000)個1,所以這兩步可以不消位,最後一步把不需要的bit位抹除就可以了:
i & 0x3f
感悟:大道至簡,看似複雜的算法,其實現原理卻是我們大腦的簡單思維邏輯
7 0b111
i = 7 - ((7>>>1) & 0x55555555) = 6 = 0b110
i = (6 & 0x33333333) + ((6 >>> 2) & 0x33333333) = 2 + 1 = 3 = 0b11
i = (3 + (i >>> 4)) & 0x0f0f0f0f = 3 & 0x0f0f0f0f = 3 = 0b11
i = 3 + (3 >>> 8) = 3 = 0b11
i = 3 + (3 >>> 16) = 3 = 0b11
i = 3 & 0x3f = 3