求二進制數中 1 的個數


                              求二進制數中 1 的個數
      大多數的讀者都會有這樣的反應:這個題目也太簡單了吧,解法似乎也相當地單一,不會有太多的曲折分析或者峰回路轉之處。 那么這個題目考察我們什么呢?事實上,在編寫程序的過程中,根據實際應用的不同,對存儲空間或效率的要求也不一樣。比如在 PC 上的程序編寫與在嵌入式設備上的程序編寫就有很大的差別。我們可以仔細思索一下如何才能使效率盡可能地“高”。


【解法一】 除法、余數
      可以舉一個八位的二進制例子來進行分析。對於二進制操作,我們知道,除以一個 2,原來的數字將會減少一個 0。如果除的過程中有余,那么就表示當前位置有一個 1。 以 10 100 010 為例; 第一次除以 2 時,商為1 010 001,余為 0。 第二次除以 2 時,商為101 000,余為 1。 因此,可以考慮利用整型數據除法的特點,通過相除和判斷余數的值來進行分析。於是有了如下的代碼。

int Count( int v)   
{  
     int num =  0;   
     while(v)   
    {   
         if(v %  2 ==  1)   
        {   
            num++;   
        }   
        v = v/  2;   
    }   
     return num;   
}

 

 

【解法二】位操作
      前面的代碼看起來比較復雜。我們知道,向右移位操作同樣也可以達到相除的目的。唯一不同之處在於,移位之后如何來判斷是否有 1 存在。對於這個問題,再來看看一個八位的數字:10 100 001。 在向右移位的過程中,我們會把最后一位直接丟棄。因此,需要判斷最后一位是否為1,而“與”操作可以達到目的。可以把這個八位的數字與00000001進行“與”操作。如果結果為1,則表示當前八位數的最后一位為1,否則為0。代碼如下:

int Count( int v)   
{   
     int num =  0;   
    While(v)   
    {   
        num += v & 0x01;   
        v >>=  1;   
    }   
     return num;   
}

 

 

 

【解法三】 位操作(修改)
      位操作比除、余操作的效率高了很多。但是,即使采用位操作,時間復雜度仍為 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。 因此就有了解法三的代碼: 

int Count( int v)   
{   
     int num =  0;   
     while(v)   
    {   
        v &= (v- 1);   
        num++;   
    }   
     return num;   
}

 

 

 

【解法四】分支操作
解法三的復雜度降低到 O(M),其中 M 是v 中1 的個數,可能會有人已經很滿足了,只用計算 1的位數,這樣應該夠快了吧。然而我們說既然只有八位數據,索性直接把 0~255的情況都羅列出來,並使用分支操作,可以得到答案,代碼如下:

 

int Count( int v)   
{   
     int num =  0;   
     switch (v)   
    {   
     case  0x0:   
        num =  0;   
         break;   
     case  0x1:   
     case  0x2:   
     case  0x4:   
     case  0x8:   
     case  0x10:   
     case  0x20:   
     case  0x40:   
     case  0x80:   
        num =  1;   
         break;   
     case  0x3:   
     case  0x6:   
     case  0xc:   
     case  0x18:   
     case  0x30:   
     case  0x60:   
     case  0xc0:   
        num =  2;   
         break;   
     // ...   
  
    }   
     return num;   
}

解法四看似很直接,但實際執行效率可能會低於解法二和解法三,因為分支語句的執行情況要看具體字節的值,如果 a =0,那自然在第 1 個case就得出了答案,但是如果 a =255,則要在最后一個 case 才得出答案,即在進行了 255 次比較操作之后! 看來,解法四不可取!但是解法四提供了一個思路,就是采用空間換時間的方法,羅列並直接給出值。如果需要快速地得到結果,可以利用空間或利用已知結論。這就好比已經知道計算 1+2+ … +N 的公式,在程序實現中就可以利用公式得到結論。 最后,得到解法五:算法中不需要進行任何的比較便可直接返回答案,這個解法在時間復雜度上應該能夠讓人高山仰止了。

 

 

 

【解法五】查表法

 

/*  預定義的結果表  */   
int countTable[ 256] =   
{   
     01121223122323341223233423,   
     3434451223233423343445233,   
     4344534454556122323342334,   
     3445233434453445455623343,   
     4453445455634454556455656,   
     6712232334233434452334344,   
     5344545562334344534454556,   
     3445455645565667233434453,   
     4454556344545564556566734,   
     4545564556566745565667566,   
     76778   
};   
int Count( int v)   
{   
     // check parameter   
     return countTable[v];   
}  

 

這是個典型的空間換時間的算法,把 0~255 中“1”的個數直接存儲在數組中,v 作為數組的下標,countTable[v]就是 v 中“1”的個數。算法的時間復雜度僅為 O(1)。 在一個需要頻繁使用這個算法的應用中,通過“空間換時間”來獲取高的時間效率是一個常用的方法,具體的算法還應針對不同應用進行優化。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM