1. 程式人生 > >判斷32位無符號整數二進位制中1的個數

判斷32位無符號整數二進位制中1的個數

1、比較簡單和容易理解的方法就是逐位比較法:

複製程式碼
#include <iostream>  
using namespace std; 
 
int findone(unsigned int n)
{ 
    for(int i=0;n>0;n>>=1) 
         i+=(n&1); 
    return i; 
} 
 
int main(){ 
  int n; 
  cin>>n; 
  cout<<findone(n)<<endl; 
  return 0; 
}
複製程式碼

這種方法的缺點是比較費時,時間長度取決於n的位數,時間複雜度O(n)。假如上千上萬位的話,每一位都要執行一遍,所用時間就很長了。

2、最直接的優化方法,其實就是空間換時間的思想:可以預建立一個表,存放了從0~2^32每個數中1的個數,用時去查一下表就知道了。但這樣顯然要耗費很多的空間(至少2^32/(256/32)=512MB,哈哈,正是一般記憶體大小)。於是需要再優化:存放0-255每個數中1的個數,然後分段查詢。如下面把32位數分為4段,每段一個位元組,所以有一個256大小供查詢的表: 

複製程式碼
char tOne[256]="\0\1\1\2\1\2……"; //後面省略  
int findone(unsigned int n){ 
  for(int i=0;n>0;n>>=8) //每次右移8位將32位分成四段
    i+=tOne[n&255
]; return i; }
複製程式碼

3、上次在阿里雲筆試,碰到一題也是求一個整數中1的個數。

int func(unsigned int n){ int count=0; while(n>0){ n&=(n-1); count++; } return count; }

比如n=10,二進位制為1010,count=2。  

4、下面這種方法據說更快,但是我覺得不容易想出來。發現很多題目都可以用位運算來快速解決,可惜本人十分討厭使用它,總覺得在繞來繞去的,偉大的位運算...

複製程式碼
int count_ones(unsigned a)
{
    a = (a & 0x55555555
) + ((a >> 1) & 0x55555555); a = (a & 0x33333333) + ((a >> 2) & 0x33333333); a = (a & 0x0f0f0f0f) + ((a >> 4) & 0x0f0f0f0f); a = (a & 0x00ff00ff) + ((a >> 8) & 0x00ff00ff); a = (a & 0x0000ffff) + ((a >> 16) & 0x0000ffff); return a; }
複製程式碼

該程式碼的思路是這樣的:2位2位為一組,相加,看看有幾個1。再4位4位為一組,相加,看看有幾個1......

為了簡單說明,先看看8位的情形。相應地,函式裡面的語句變成。
x = (x & 0x55) + ((x >> 1) & 0x55);    (1)
x = (x & 0x33) + ((x >> 2) & 0x33);    (2)
x = (x & 0x0f) + ((x >> 4) & 0x0f);    (3)
return x;

假設x=abcdefgh. 0x55=01010101
x & 0x55 = 0b0d0f0h.   (x>>1) & 0x55 = 0a0c0e0g。相加。就可以知道2位2位一組1的個數。

比如x=11111111
x= (x & 0x55) + ((x >> 1) & 0x55); 之後x=10101010。你2位2位地看,10=2, 就是2 2 2 2, 就是說各組都是2個1。
比如x=00101001
x= (x & 0x55) + ((x >> 1) & 0x55); 之後x=00010101。你2位2位地看,就是0 1 1 1, 前1組只有0個1,後面的組都是1個1。

好啦。再來看。0x33=00110011。
x=abcdefgh. 
x=(x & 0x33)+((x >> 2)&0x33); 相當於, 00ab00ef + 00cd00gh。
因為語句(1)之後。ab指示了頭兩位有多少個1,cd指示了下兩位有多少個1。相加00ab+00cd就指示前4位有多少個1。這樣就是4位4位為一組。注意這樣的分組,組與組之間永遠都不會產生進位的。正因為不會產生進位,才可以分開來看。

下面的過程都是一樣的,不再多說。8位,16位,32位都一樣。