統計二進位制展開中數位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; //用於儲存找到的每一個檔
統計一行文字的單詞個數 (15 分) 本題目要求編寫程式統計一行字元中單詞的個數。所謂“單詞”是指連續不含空格的字串,各單詞之間用空格分隔,空格數可以是多個。 輸入格式: 輸入給出一行字元。 輸出格式: 在一行中輸出單詞個數。 輸入樣例: Let's go to room 209. 輸出樣例
MD,一開始就想著怎麼 用空格和結尾前判斷字母 來計算寫的頭的爆了, 反過來判斷空格後面是否有 =‘ ’就尼瑪容易多了 #include<stdio.h> #include<stdlib.h> #include<string.h> int
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);
Java中IO流-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