第2章 数字之魅——求二进制中1的个数
求二進制中1的個數
問題描述
對于一個字節(8bit)的變量,求其二進制表示中“1”的個數,要求算法的執行效率盡可能地高。
【解法一】
可以舉一個八位的二進制例子來進行分析。對于二進制操作,我們知道,除以一個2,原來的數字將會減少一個0。如果除的過程中有余,那么就表示當前位置有一個1。
以10 100 010為例;
第一次除以2時,商為1 010 001,余為0。
第二次除以2時,商為101 000,余為1。
因此,可以考慮利用整型數據除法的特點,通過相除和判斷余數的值來進行分析。于是有了如下的代碼:
1 package chapter2shuzizhimei.erjinzhicount1; 2 /** 3 * 求二進制數中1的個數 4 * 【方法一】 5 * @author DELL 6 * 7 */ 8 public class Count1 { 9 10 /** 11 * 統計整數x轉變成二進制后1的個數 12 * @param x 被計算的整數 13 * @return 二進制中1的個數 14 */ 15 public static int count(int x){ 16 int count = 0; //記錄二進制中1的個數 17 while(x!=0){ 18 if(x%2==1){ 19 count++; 20 } 21 x /= 2; 22 } 23 return count; 24 } 25 26 public static void main(String[] args){ 27 int x = 15; 28 System.out.println("二進制中1的個數為:"+count(x)); 29 } 30 }程序運行結果如下:
二進制中1的個數為:4?【解法二】采用位操作
前面的代碼看起來比較復雜。我們知道,向右移位操作同樣也可以達到相除的目的。唯一不同之處在于,移位之后如何來判斷是否有1存在。對于這個問題,再來看看一個八位的數字:10 100 001。
在向右移位的過程中,我們會把最后一位直接丟棄。因此,需要判斷最后一位是否為1,而"與"操作可以達到目的。可以把這個八位的數字與00000001進行"與"操作。如果結果為1,則表示當前八位數的最后一位為1,否則為0。代碼如下:
1 package chapter2shuzizhimei.erjinzhicount1; 2 /** 3 * 求二進制數中1的個數 4 * 【方法二】采用位操作 5 * @author DELL 6 * 7 */ 8 public class Count2 { 9 10 /** 11 * 統計整數x轉變成二進制后1的個數 12 * @param x 被計算的整數 13 * @return 二進制中1的個數 14 */ 15 public static int count(int x){ 16 int count = 0; //記錄二進制中1的個數 17 while(x!=0){ 18 count += x&0x01; //采用與運算來統計1的個數 19 x >>= 1; 20 } 21 return count; 22 } 23 24 public static void main(String[] args){ 25 int x = 15; 26 System.out.println("二進制中1的個數為:"+count(x)); 27 } 28 }程序運行結果如下:
二進制中1的個數為:4?【解法三】 只考慮1的個數
位操作比除、余操作的效率高了很多。但是,即使采用位操作,時間復雜度仍為O(log2v),log2v為二進制數的位數。那么,還能不能再降低一些復雜度呢?如果有辦法讓算法的復雜度只與"1"的個數有關,復雜度不就能進一步降低了嗎?
同樣用10 100 001來舉例。如果只考慮和1的個數相關,那么,我們是否能夠在每次判斷中,僅與1來進行判斷呢?
為了簡化這個問題,我們考慮只有一個1的情況。例如:01 000 000。
如何判斷給定的二進制數里面有且僅有一個1呢?可以通過判斷這個數是否是2的整數次冪來實現。另外,如果只和這一個"1"進行判斷,如何設計操作呢?我們知道的是,如果進行這個操作,結果為0或為1,就可以得到結論。
如果希望操作后的結果為0,01 000 000可以和00 111 111進行"與"操作。
這樣,要進行的操作就是 01 000 000 &(01 000 000 - 00 000 001)= 01 000 000 &00 111 111 = 0。
因此就有了解法三的代碼:
1 package chapter2shuzizhimei.erjinzhicount1; 2 /** 3 * 求二進制數中1的個數 4 * 【方法三】只考慮1的個數減少時間復雜度 5 * @author DELL 6 * 7 */ 8 public class Count3 { 9 10 /** 11 * 統計整數x轉變成二進制后1的個數 12 * @param x 被計算的整數 13 * @return 二進制中1的個數 14 */ 15 public static int count(int x){ 16 int count = 0; //記錄二進制中1的個數 17 while(x!=0){ 18 x &= (x-1); //消掉最高位的1 19 count++; 20 } 21 return count; 22 } 23 24 public static void main(String[] args){ 25 int x = 15; 26 System.out.println("二進制中1的個數為:"+count(x)); 27 } 28 }程序運行結果如下:
二進制中1的個數為:4?
【解法四】使用分支操作
解法三的復雜度降低到O(M),其中M是v中1的個數,可能會有人已經很滿足了,只用計算1的位數,這樣應該夠快了吧。然而我們說既然只有八位數據,索性直接把0~255的情況都羅列出來,并使用分支操作,可以得到答案,代碼如下:
1 int count(int x) 2 { 3 int num = 0; 4 switch (x) 5 { 6 case 0x0: 7 num = 0; 8 break; 9 case 0x1: 10 case 0x2: 11 case 0x4: 12 case 0x8: 13 case 0x10: 14 case 0x20: 15 case 0x40: 16 case 0x80: 17 num = 1; 18 break; 19 case 0x3: 20 case 0x6: 21 case 0xc: 22 case 0x18: 23 case 0x30: 24 case 0x60: 25 case 0xc0: 26 num = 2; 27 break; 28 //... 29 } 30 return num; 31 }解法四看似很直接,但實際執行效率可能會低于解法二和解法三,因為分支語句的執行情況要看具體字節的值,如果a =0,那自然在第1個case就得出了答案,但是如果a =255,則要在最后一個case才得出答案,即在進行了255次比較操作之后!
看來,解法四不可取!但是解法四提供了一個思路,就是采用空間換時間的方法,羅列并直接給出值。如果需要快速地得到結果,可以利用空間或利用已知結論。這就好比已經知道計算1+2+ … +N的公式,在程序實現中就可以利用公式得到結論。
【解法五】查表法
算法中不需要進行任何的比較便可直接返回答案,這個解法在時間復雜度上應該能夠讓人高山仰止了。
代碼如下:
1 int countTable[256] = 2 { 3 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 4 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 5 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 6 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 7 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 8 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 9 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 10 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 11 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 12 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 13 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 14 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 15 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 16 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 17 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 18 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8 19 }; 20 21 int count(int v) 22 { 23 return countTable[v]; 24 }?
這是個典型的空間換時間的算法,把0~255中"1"的個數直接存儲在數組中,v作為數組的下標,countTable[v]就是v中"1"的個數。算法的時間復雜度僅為O(1)。
在一個需要頻繁使用這個算法的應用中,通過"空間換時間"來獲取高的時間效率是一個常用的方法,具體的算法還應針對不同應用進行優化。
擴展問題
1.?如果變量是32位的DWORD,你會使用上述的哪一個算法,或者改進哪一個算法?
2.?另一個相關的問題,給定兩個正整數(二進制形式表示)A和B,問把A變為B需要改變多少位(bit)?也就是說,整數A 和B 的二進制表示中有多少位是不同的?
這個問題其實就是比問題1多了一個步驟,只要先算出A和B的異或結果,然后求這個異或值中1的個數就行了。
總結
以上是生活随笔為你收集整理的第2章 数字之魅——求二进制中1的个数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: thinkphp框架使用心得
- 下一篇: c++ 哪些自定义的数据类型