1. 程式人生 > >統計二進位制展開中數位1的個數的優化

統計二進位制展開中數位1的個數的優化

問題:

  對於任意的非負整數,統計其二進位制展開中數位1的總數。

解決:

  在看這篇之前可以先看看上述這篇,這篇主要討論其優化問題。

常規解法:

O(logn):

 1 int countOnes(unsigned int n)
 2 {
 3     int ones = 0;
 4     while (0 < n)
 5     {
 6         ones += (1 & n);
 7         n >>= 1;
 8     }
 9     return ones;
10 }

無非就是每次取其二進位制展開最後一位,是1就計數。

效率由位運算可知(右移一位等價於除以2),為 O(logn)。

優化解法1:

O(countOnes(n)):

 1 int countOnes1(unsigned int n)
 2 {
 3     int ones = 0;
 4     while (0 < n)
 5     {
 6         ones++;            // 計數(最後至少有一位為1)
 7         n &= n - 1;        // 清除當前最靠右的1
 8     }
 9     return ones;
10 }

解釋如下:

優化解法2:

O(logW), W = O(logn) 為整數的位寬, 實際上就是 O(1) 的演算法

程式碼及解釋:

這個演算法是一種合併計數器的策略。把輸入數的32Bit當作32個計數器,代表每一位的1個數。然後合併相鄰的2個“計數器”,使i成為16個計數器,每個計數器的值就是這2個Bit的1的個數;繼續合併相鄰的2個“計數器“,使i成為8個計數器,每個計數器的值就是4個Bit的1的個數。。依次類推,直到將i變成一個計數器,那麼它的值就是32Bit的i中值為1的Bit的個數。

實際上還是二分的思想,把一位一位計數變成二分的計數,使 O(logn) 變成了 O(loglogn)。

為了理解起來方便,程式碼可簡化為:

 1 int BitCount4(unsigned int n) 
 2 { 
 3     n = (n &0x55555555) + ((n >>1) &0x55555555) ; 
 4     n = (n &0x33333333) + ((n >>2) &0x33333333) ; 
 5     n = (n &0x0f0f0f0f) + ((n >>4) &0x0f0f0f0f) ; 
 6     n = (n &0x00ff00ff) + ((n >>8) &0x00ff00ff) ; 
 7     n = (n &0x0000ffff) + ((n >>16) &0x0000ffff) ; 
 8 
 9     return n ; 
10 }

其他的一些有助於理解的解釋有:

說簡單點,就是一個 錯位分段相加,然後遞迴合併的過程 。

下面是細節分析:

首先先看看那些詭異的數字都有什麼特點:
0x5555……這個換成二進位制之後就是0101010101010101……
0x3333……這個換成二進位制之後就是0011001100110011……
0x0f0f……...這個換成二進位制之後就是0000111100001111……
看出來點什麼了嗎?
如果把這些二進位制序列看作一個迴圈的週期序列的話,

那麼第一個序列的週期是2,每個週期是01,第二個序列的週期是4,每個週期是0011,第三個的週期是8,每個是00001111……

這樣的話,我們可以看看如果一個數和這些玩意相與之後的結果:

整個數按照上述的週期被分成了n段,每段裡面的前半截都被清零,後半截保留了資料。不同在於這些數分段的長度是2倍增長的。於是我們可以姑且命名它們為“分段擷取常數”。

這樣,如果我們按照分段的思想,每個週期分成一段的話,你或許就可以感覺到這個分段是二分法的倒過來——類似二段合併一樣的東西!


現 在回頭來看問題,我們要求的是1的個數。這就要有一個清點並相加的過程(查表法除外)。使用&運算和移位運算可以幫我們找到1,但是卻無法計算1 的個數,需要由加法來完成。最傳統的逐位查詢並相加,每次只加了1位,顯然比較浪費,我們能否一次用加法來計算多次的位數呢?

再考慮問題,找到了1的位置,如何把這個位置變成數量。最簡單的情況,一個2位的數,比如11,只要把它的第二位和第一位相加,不就得到了1的個數了嗎?!所以對於2位的x,有x中1的個數=(x>>1)+(x&1)。是不是和上面的式子有點像?

再考慮稍複雜的,一個位元組內的情況。
一個位元組的x,顯然不能用(x>>1)+(x&1)的方法來完成,但是我們受到了啟發,如果把x分段相加呢?把x分成4個2位的段,然後相加,就會產生4個2位的數,每個都代表了x對應2位地方的1的個數。


例子,若求156中1的個數,156二進位制是10011100
最終:

[1][0][0][1][1][1][0][0] //初始,每一位是一組
---
|0  0 |0  1 |0  1 |0  0|  //與01010101相與的結果,同時2個一組分組
+
|0  1 |0  0 |0  1 |0  0|  //右移一位後與01010101相與的結果
=
[0  1][0  1][1  0][0  0]  //相加完畢後,現在每2位是一組,每一組儲存的都是最初在這2位的1的個數
----
|0  0  0  1 |0  0  0  0|  //與00110011相與的結果,4個一組分組
+
|0  0  0  1 |0  0  1  0|  //右移兩位後與00110011相與的結果
=
[0  0  1  0][0  0  1  0] //相加完畢後,現在每4位是一組,並且每組儲存的都是最初這4位的1的個數
----
|0  0  0  0  0  0  1  0|
+
|0  0  0  0  0  0  1  0|
=
[0  0  0  0  0  1  0  0] //最終合併為8位1組,儲存的是整個數中1的個數,即4。

比如這個例子,143的二進位制表示是10001111,這裡只有8位,高位的0怎麼進行與的位運算也是0,所以只考慮低位的運算,按照這個演算法走一次

+---+---+---+---+---+---+---+---+
| 1 | 0 | 0 | 0 | 1 | 1 | 1 | 1 |   <---143
+---+---+---+---+---+---+---+---+
|  0 1  |  0 0  |  1 0  |  1 0  |   <---第一次運算後
+-------+-------+-------+-------+
|    0 0 0 1    |    0 1 0 0    |   <---第二次運算後
+---------------+---------------+
|        0 0 0 0 0 1 0 1        |   <---第三次運算後,得數為5
+-------------------------------+

這裡運用了分治的思想,先計算每對相鄰的2位中有幾個1,再計算每相鄰的4位中有幾個1,下來8位,16位,32位,因為2^5=32,所以對於32位的機器,5條位運算語句就夠了。

像這裡第二行第一個格子中,01就表示前兩位有1個1,00表示下來的兩位中沒有1,其實同理。再下來01+00=0001表示前四位中有1個1,同樣的10+10=0100表示低四位中有4個1,最後一步0001+0100=00000101表示整個8位中有5個1。

再舉一個例子:(來源:維基百科)

例如,要計算二進位制數 A=0110110010111010 中 1 的個數,這些運算可以表示為:

符號 二進位制 十進位制 註釋
A 0110110010111010 原始資料
B = A & 01 01 01 01 01 01 01 01 01 00 01 00 00 01 00 00 1,0,1,0,0,1,0,0 A 隔一位檢驗
C = (A >> 1) & 01 01 01 01 01 01 01 01 00 01 01 00 01 01 01 01 0,1,1,0,1,1,1,1 A 中剩餘的資料位
D = B + C 01 01 10 00 01 10 01 01 1,1,2,0,1,2,1,1 A 中每個雙位段中 1 的個數列表
E = D & 0011 0011 0011 0011 0001 0000 0010 0001 1,0,2,1 D 中資料隔一位檢驗
F = (D >> 2) & 0011 0011 0011 0011 0001 0010 0001 0001 1,2,1,1 D 中剩餘資料的計算
G = E + F 0010 0010 0011 0010 2,2,3,2 A 中 4 位資料段中 1 的個數列表
H = G & 00001111 00001111 00000010 00000010 2,2 G 中資料隔一位檢驗
I = (G >> 4) & 00001111 00001111 00000010 00000011 2,3 G 中剩餘資料的計算
J = H + I 00000100 00000101 4,5 A 中 8 位資料段中 1 的個數列表
K = J & 0000000011111111 0000000000000101 5 J 中隔一位檢驗
L = (J >> 8) & 0000000011111111 0000000000000100 4 J 中剩餘資料的檢驗
M = K + L 0000000000001001 9 最終答案

Reference:

相關推薦

統計二進位制展開數位1個數優化

問題:   對於任意的非負整數,統計其二進位制展開中數位1的總數。 解決:   在看這篇之前可以先看看上述這篇,這篇主要討論其優化問題。 常規解法: O(logn): 1 int countOnes(unsigned int n) 2 { 3 int

c語言統計二進位制1個數的演算法優化

統計整數二進位制位中1的個數的辦法:int one(int m) {  int count = 0;  while (m != 0)  {   if (m % 2 == 1)   //進行模2除2一位一位的統計   {    count++;   }   m

統計二進位制1個數

思路:定義n表示1的個數,一個二進位制數按位遍歷一遍,並且每一位按位與1,結果為1,則n加1,輸出n即為結果。 程式碼: #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<string.

二進位制1個數

第一種最常見的解法: 原理是:採用C語言十進位制轉換成二進位制的方法去解題 程式程式碼如下: #include "Count.h" int Count(char b) { int number = 0; while (b) {

二進位制返回1個數的幾種方法

方法一:採用先模2再除2的方法,例如13模2結果是1,這個1就是二進位制數中最後一個1,再除以2去除這一個位1,以此往復循化,當二進位制數全是零的時候就沒有1了,迴圈結束。但是此方法只適用於正數。 int main() { int num = 13;//1101 int cou

計算二進位制整數1個數

如果要計算一個整數的二進位制數中共有幾個1,最容易想到的方法就是下面這個方法:     while (num)     {         if (num % 2 == 1) &n

利用異或判斷二進位制1個數的奇偶性

文章目錄 異或壓縮奇偶性資訊 一位一位地異或 利用二叉樹思想異或 關於有符號數和算術右移 利用x &= x-1求二進位制1個數 利用邏輯右移求二進位制1個數 兩個二進位制數異或後結果的1個數的奇偶性 異或

計算一個二進位制數字“1”的個數(位運算)

int numberOfOne( unsigned value ) { int count; for( count = 0; value != 0; value >>= 1 ) if( ( value & 1 ) != 0 )//如果最低位是1,就增加計數器的

計算一個自然數的二進位制表示的“1”的個數

1 /*  *  計算一個自然數的二進位制表示中的“1”的個數  *  用遞迴演算法  */ public class recursionTest { public static void main(String[] args) { for(

【面試題】劍指offer10--求一個數二進位制1個數

求一個數二進位制數中的個數 第一種方法:模除法 程式碼如下: //Q:請實現一個函式,輸入一個整數,輸出該數二進位制中的 //1的個數。例如:把9表示成二進位制是1001,有2位是1.因此,如果輸入

使用tuple統計文件單詞的個數

dict sort txt () col div pri 文件中 turn 1 name = input("Enter file:") 2 if len(name) < 1 : name = "input.txt" 3 fhand = open(name)

[Java]統計指定目錄檔案的個數和總的大小

題目 給定一個指定的目錄,例如"E:\音樂",求出該目錄下檔案的總數,以及所有檔案加起來的大小. ·複習了File類的使用方法 ·複習了使用遞迴演算法查詢檔案 程式碼實現 說明 ArrayList<File> fileList; //用於儲存找到的每一個檔

LeetCode 給定一個非負整數 num。對於 0 ≤ i ≤ num 範圍的每個數字 i ,計算其二進位制1 的數目並將它們作為陣列返回。

/**  * Return an array of size *returnSize.  * Note: The returned array must be malloced, assume caller calls free().  */ int* countBit

判斷二進位制1有奇數個還是偶數個

判斷(32位)整數的二進位制表示中的1有奇數個還是偶數個 最直接的思路就是求二進位制數中1的個數,然後確定是偶數還是奇數。 程式碼如下: // true為x二進位制表示中含有奇數個1,false為偶數個1 bool OddOnes(int x)    {       

統計一個字串單詞的個數(C語言)

#include<stdio.h>  #include<stdlib.h>  int main()  {     int num = 0, word = 0;     char *p = NULL;     p = (char *)malloc(sizeof(char)*100);  

JavaIO流-29-IO流練習題:統計一個檔案字元出現個數

這篇來利用IO流知識來做兩個練習題。第一個題目是在文字檔案中統計字元出現次數,並寫入到一個txt檔案裡。第二個練習題是模擬,試用軟體30天倒計時,這裡我們簡化一下,執行一次程式碼,試用天就減去1天。 1.文字檔案內統計字元出現個數 題目:給定一個文字檔案,統計字元出現個數

程式設計之美---求N!的二進位制表示最低位1的位置

問題描述: 求N!的二進位制表示中最低位1的位置。例如:給定N=3,N!=6,那麼N!的二進位制表示(1010)的最低位1在第二位。 問題求解: 這個問題等同於求N!含有質因數2的個數,因為二進位

程式設計師面試100題之八:不要被階乘嚇倒(二進位制表示最低位1的位置 )

http://blog.csdn.net/hackbuteer1/article/details/6690015 階乘(Factorial)是個很有意思的函式,但是不少人都比較怕它,我們來看看兩個與階乘相關的問題: 1、 給定一個整數N,那麼N的階乘N!末尾有多少個0

寫一個函式返回引數二進位制中1個數+獲取一個數二進位制序列所有的偶數位和奇數位,分別輸出二進位制序列+輸出一個整數的每一位+兩個int(32位)整數m和n的二進位制表達,有多少個位(bit)不同

寫一個函式返回引數二進位制中 1 的個數 比如: 15 0000 1111 4 個 1 #include <stdio.h> #include <windows.h> /* 寫一個函式統計一個數二進位制形式下 1 的個數 */ //統計 1 的個數 int C