一. 位操作基礎
位運算符分為邏輯運算符(~、|、&、^)和移位運算符(<<、>>、>>>)。位運算操作的是二進制的數。
邏輯運算符
1、^(亦或運算) ,針對二進制,相同的為0,不同的為1。
2、&(與運算) 針對二進制,只要有一個為0,就為0
3、| 兩個位只要有一個為1,那么結果就是1,否則就為0
4、~ 取反
5、原碼 原碼就是符號位加上數字的二進制表示。
6、反碼
一個數如果為正,則它的反碼與原碼相同;一個數如果為負,則符號位為1,(符號位不變化,其余位數取反)。
換言之 該數的絕對值取反(絕對值取反各位都取反)。
為了簡單起見,我們用1個字節來表示一個整數:
+7的反碼為:00000111
-7的反碼為: 11111000
7、 補碼
補碼:一個數如果為正,則它的原碼、反碼、補碼相同;一個數如果為負,取到反碼然后加1。(反碼加1就是補碼)為了簡單起見,我們用1個字節來表示一個整數:
+7的補碼為: 00000111
-7的補碼為: 11111001
移位運算符
1、<<:左移,將運算符左邊的運算對象向左移動右側指定的位數。注意,是二進制表示的時候,相當於運算符左側的運算對象乘以2(在不溢出的情況下),二進制形式最低位補0。
2、>>:右移運算符,將運算符右邊的運算對象向右移動指定的位數。相當於將運算符左邊的數除以2,二進制形式對應的最高位補上符號位。(負數的二進制表示形式為其補碼)
3、>>>:無符號右移運算符,和右移運算一樣,但是二進制表示時最高位補上時無論被操作數是正是負,補上的都是0。
位運算優先級
~的優先級最高,其次是<<、>>和>>>,再次是&,然后是^,優先級最低的是|。
注意
若對char,byte或者short進行移位處理,那么在移位進行之前,先將它們轉化成int。 只有右側的5個低位才會用到。這樣可防止我們在一個int數里移動不切實際的位數。 若對一個long值進行處理,最后得到的結果也是long。此時只會用到右側的6個低位,防止移動超過long值里現成的位數。 但在進行“無符號”右移位時,也可能遇到一個問題。若對byte或short值進行右移位運算,得到的可能不是正確的結果,它們會自動轉換成int類型,並進行右移位。但“零擴展”不會發生,所以在那些情況下會得到-1的結果。
8、復合賦值運算符
位運算符與賦值運算符結合,組成新的復合賦值運算符,它們是:
1、&= 例:a &=b 相當於a=a& b
2、|= 例:a |=b 相當於a=a |b
3、>>= 例:a >>=b 相當於a=a>> b
4、<<= 例:a<<=b 相當於a=a<< b
5、^= 例:a ^= b 相當 a=a ^b
運算規則:和前面講的復合賦值運算符的運算規則相似。
9、不同長度的數據進行位運算
如果兩個不同長度的數據進行位運算時,系統會將二者按右端對齊,然后進行位運算。
以“與”運算為例說明如下:如果一個4個字節的數據與一個2個字節數據進行“與”運算,右端對齊后,左邊不足的位依下面三種情況補足:
(1)如果整型數據為正數,左邊補16個0。
(2)如果整型數據為負數,左邊補16個1。
(3)如果整形數據為無符號數,左邊也補16個0。
二. 常用位操作小技巧
2.1 判斷奇偶
只要根據最未位是0還是1來決定,為0就是偶數,為1就是奇數。因此可以用if (a & 1 == 0)代替if (a % 2 == 0)來判斷a是不是偶數。
下面程序將輸出0到100之間的所有奇數。
1 for (i = 0; i < 100; ++i) 2 if (i & 1) 3 printf("%d ", i); 4 putchar('\n');
2.2 交換兩數
一般的寫法是:
1 void Swap(int &a, int &b) 2 { 3 if (a != b) 4 { 5 int c = a; 6 a = b; 7 b = c; 8 } 9 }
可以用位操作來實現交換兩數而不用第三方變量:
1 void Swap(int &a, int &b) 2 { 3 if (a != b) 4 { 5 a ^= b; 6 b ^= a; 7 a ^= b; 8 } 9 }
可以這樣理解:
第一步 a^=b 即a=(a^b);
第二步 b^=a 即b=b^(a^b),由於^運算滿足交換律,b^(a^b)=b^b^a。由於一個數和自己異或的結果為0並且任何數與0異或都會不變的,所以此時b被賦上了a的值。
第三步 a^=b 就是a=a^b,由於前面二步可知a=(a^b),b=a,所以a=a^b即a=(a^b)^a。故a會被賦上b的值。
再來個實例說明下以加深印象。int a = 13, b = 6;
a的二進制為 13=8+4+1=1101(二進制)
b的二進制為 6=4+2=110(二進制)
第一步 a^=b a = 1101 ^ 110 = 1011;
第二步 b^=a b = 110 ^ 1011 = 1101;即b=13
第三步 a^=b a = 1011 ^ 1101 = 110;即a=5
2.3 變換符號
變換符號就是正數變成負數,負數變成正數。
如對於-11和11,可以通過下面的變換方法將-11變成11
1111 0101(二進制) –取反-> 0000 1010(二進制) –加1-> 0000 1011(二進制)
同樣可以這樣的將11變成-11
0000 1011(二進制) –取反-> 0000 1010(二進制) –加1-> 1111 0101(二進制)
因此變換符號只需要取反后加1即可。完整代碼如下:
1 //by MoreWindows( http://blog.csdn.net/MoreWindows )
2 #include <stdio.h>
3 int SignReversal(int a) 4 { 5 return ~a + 1; 6 } 7 int main() 8 { 9 printf("對整數變換符號 --- by MoreWindows( http://blog.csdn.net/MoreWindows ) ---\n\n"); 10 int a = 7, b = -12345; 11 printf("%d %d\n", SignReversal(a), SignReversal(b)); 12 return 0; 13 }
2.4 求絕對值
位操作也可以用來求絕對值,對於負數可以通過對其取反后加1來得到正數。對-6可以這樣:
1111 1010(二進制) –取反->0000 0101(二進制) -加1-> 0000 0110(二進制)
來得到6。
因此先移位來取符號位,int i = a >> 31;要注意如果a為正數,i等於0,為負數,i等於-1。然后對i進行判斷——如果i等於0,直接返回。否之,返回~a+1。完整代碼如下:
1 //by MoreWindows( http://blog.csdn.net/MoreWindows )
2 int my_abs(int a) 3 { 4 int i = a >> 31; 5 return i == 0 ? a : (~a + 1); 6 }
現在再分析下。對於任何數,與0異或都會保持不變,與-1即0xFFFFFFFF異或就相當於取反。因此,a與i異或后再減i(因為i為0或-1,所以減i即是要么加0要么加1)也可以得到絕對值。所以可以對上面代碼優化下:
1 //by MoreWindows( http://blog.csdn.net/MoreWindows )
2 int my_abs(int a) 3 { 4 int i = a >> 31; 5 return ((a ^ i) - i); 6 }
注意這種方法沒用任何判斷表達式,而且有些筆面試題就要求這樣做,因此建議讀者記住該方法(^_^講解過后應該是比較好記了)
2.5 位操作與空間壓縮
篩素數法在這里不就詳細介紹了,本文着重對篩素數法所使用的素數表進行優化來減小其空間占用。要壓縮素數表的空間占用,可以使用位操作。下面是用篩素數法計算100以內的素數示例代碼(注2):
1 //by MoreWindows( http://blog.csdn.net/MoreWindows )
2 #include <stdio.h>
3 #include <memory.h>
4 const int MAXN = 100; 5 bool flag[MAXN]; 6 int primes[MAXN / 3], pi; 7 //對每個素數,它的倍數必定不是素數。 8 //有很多重復如flag[10]會在訪問flag[2]和flag[5]時各訪問一次
9 void GetPrime_1() 10 { 11 int i, j; 12 pi = 0; 13 memset(flag, false, sizeof(flag)); 14 for (i = 2; i < MAXN; i++) 15 if (!flag[i]) 16 { 17 primes[pi++] = i; 18 for (j = i; j < MAXN; j += i) 19 flag[j] = true; 20 } 21 } 22 void PrintfArray() 23 { 24 for (int i = 0; i < pi; i++) 25 printf("%d ", primes[i]); 26 putchar('\n'); 27 } 28 int main() 29 { 30 printf("用篩素數法求100以內的素數\n-- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n"); 31 GetPrime_1(); 32 PrintfArray(); 33 return 0; 34 }
運行結果如下:
在上面程序是用bool數組來作標記的,bool型數據占1個字節(8位),因此用位操作來壓縮下空間占用將會使空間的占用減少八分之一。
下面考慮下如何在數組中對指定位置置1,先考慮如何對一個整數在指定位置上置1。對於一個整數可以通過將1向左移位后與其相或來達到在指定位上置1的效果,代碼如下所示:
1 //在一個數指定位上置1
2 int j = 0; 3 j |= 1 << 10; 4 printf("%d\n", j);
同樣,可以1向左移位后與原數相與來判斷指定位上是0還是1(也可以將原數右移若干位再與1相與)。
1 //判斷指定位上是0還是1
2 int j = 1 << 10; 3 if ((j & (1 << 10)) != 0) 4 printf("指定位上為1"); 5 else
6 printf("指定位上為0");
擴展到數組上,我們可以采用這種方法,因為數組在內存上也是連續分配的一段空間,完全可以“認為”是一個很長的整數。先寫一份測試代碼,看看如何在數組中使用位操作:
1 //by MoreWindows( http://blog.csdn.net/MoreWindows )
2 #include <stdio.h>
3 int main() 4 { 5 printf(" 對數組中指定位置上置位和判斷該位\n"); 6 printf("--- by MoreWindows( http://blog.csdn.net/MoreWindows ) ---\n\n"); 7 //在數組中在指定的位置上寫1
8 int b[5] = {0}; 9 int i; 10 //在第i個位置上寫1
11 for (i = 0; i < 40; i += 3) 12 b[i / 32] |= (1 << (i % 32)); 13 //輸出整個bitset
14 for (i = 0; i < 40; i++) 15 { 16 if ((b[i / 32] >> (i % 32)) & 1) 17 putchar('1'); 18 else
19 putchar('0'); 20 } 21 putchar('\n'); 22 return 0; 23 }
可以看出該數組每3個就置成了1,證明我們上面對數組進行位操作的方法是正確的。因此可以將上面篩素數方法改成使用位操作壓縮后的篩素數方法:
1 //使用位操作壓縮后的篩素數方法 2 //by MoreWindows( http://blog.csdn.net/MoreWindows )
3 #include <stdio.h>
4 #include <memory.h>
5 const int MAXN = 100; 6 int flag[MAXN / 32]; 7 int primes[MAXN / 3], pi; 8 void GetPrime_1() 9 { 10 int i, j; 11 pi = 0; 12 memset(flag, 0, sizeof(flag)); 13 for (i = 2; i < MAXN; i++) 14 if (!((flag[i / 32] >> (i % 32)) & 1)) 15 { 16 primes[pi++] = i; 17 for (j = i; j < MAXN; j += i) 18 flag[j / 32] |= (1 << (j % 32)); 19 } 20 } 21 void PrintfArray() 22 { 23 for (int i = 0; i < pi; i++) 24 printf("%d ", primes[i]); 25 putchar('\n'); 26 } 27 int main() 28 { 29 printf("用位操作壓縮后篩素數法求100以內的素數\n-- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n"); 30 GetPrime_1(); 31 PrintfArray(); 32 return 0; 33 }
另外,還可以使用C++ STL中的bitset類來作素數表。篩素數方法在筆試面試出現的幾率還是比較大的,能寫出用位操作壓縮后的篩素數方法無疑將會使你的代碼脫穎而出,因此強烈建議讀者自己親自動手實現一遍,平時多努力,考試才不慌。
2.6 高低位交換
給出一個16位的無符號整數。稱這個二進制數的前8位為“高位”,后8位為“低位”。現在寫一程序將它的高低位交換。例如,數34520用二進制表示為:
10000110 11011000
將它的高低位進行交換,我們得到了一個新的二進制數:
11011000 10000110
它即是十進制的55430。
這個問題用位操作解決起來非常方便,設x=34520=10000110 11011000(二進制) 由於x為無符號數,右移時會執行邏輯右移即高位補0,因此x右移8位將得到0000000010000110。而x左移8位將得到11011000 00000000。可以發現只要將x>>8與x<<8這兩個數相與就可以得到11011000 10000110。用代碼實現非常簡潔:
1 //高低位交換 by MoreWindows( http://blog.csdn.net/MoreWindows )
2 #include <stdio.h>
3 template <class T>
4 void PrintfBinary(T a) 5 { 6 int i; 7 for (i = sizeof(a) * 8 - 1; i >= 0; --i) 8 { 9 if ((a >> i) & 1) 10 putchar('1'); 11 else
12 putchar('0'); 13 if (i == 8) 14 putchar(' '); 15 } 16 putchar('\n'); 17 } 18 int main() 19 { 20 printf("高低位交換 --- by MoreWindows( http://blog.csdn.net/MoreWindows ) ---\n\n"); 21
22 printf("交換前: "); 23 unsigned short a = 3344520; 24 PrintfBinary(a); 25
26 printf("交換后: "); 27 a = (a >> 8) | (a << 8); 28 PrintfBinary(a); 29 return 0; 30 }
2.7 二進制逆序
我們知道如何對字符串求逆序,現在要求計算二進制的逆序,如數34520用二進制表示為:
10000110 11011000
將它逆序,我們得到了一個新的二進制數:
00011011 01100001
它即是十進制的7009。
回顧下字符串的逆序,可以從字符串的首尾開始,依次交換兩端的數據。在二進制逆序我們也可以用這種方法,但運用位操作的高低位交換來處理二進制逆序將會得到更簡潔的方法。類似於歸並排序的分組處理,可以通過下面4步得到16位數據的二進制逆序:
第一步:每2位為一組,組內高低位交換
10 00 01 10 11 01 10 00
-->01 00 10 01 11 10 01 00
第二步:每4位為一組,組內高低位交換
0100 1001 1110 0100
-->0001 0110 1011 0001
第三步:每8位為一組,組內高低位交換
00010110 10110001
-->01100001 00011011
第四步:每16位為一組,組內高低位交換
01100001 00011011
-->00011011 01100001
對第一步,可以依次取出每2位作一組,再組內高低位交換,這樣有點麻煩,下面介紹一種非常有技巧的方法。先分別取10000110 11011000的奇數位和偶數位,空位以下划線表示。
原 數 10000110 11011000
奇數位 1_0_0_1_ 1_0_1_0_
偶數位 _0_0_1_0 _1_1_0_0
將下划線用0填充,可得
原 數 10000110 11011000
奇數位 10000010 10001000
偶數位 00000100 01010000
再將奇數位右移一位,偶數位左移一位,此時將這兩個數據相與即可以達到奇偶位上數據交換的效果了。
原 數 10000110 11011000
奇數位右移 01000011 01101100
偶數位左移 0000100 010100000
相與得到 01001000 11100100
可以看出,結果完全達到了奇偶位的數據交換,再來考慮代碼的實現——
取x的奇數位並將偶數位用0填充用代碼實現就是x & 0xAAAA
取x的偶數位並將奇數位用0填充用代碼實現就是x & 0x5555
因此,第一步就用代碼實現就是:
x = ((x & 0xAAAA) >> 1) | ((x & 0x5555) << 1);
類似可以得到后三步的代碼。完整程序如下:
1 //二進制逆序 by MoreWindows( http://blog.csdn.net/MoreWindows )
2 #include <stdio.h>
3 template <class T>
4 void PrintfBinary(T a) 5 { 6 int i; 7 for (i = sizeof(a) * 8 - 1; i >= 0; --i) 8 { 9 if ((a >> i) & 1) 10 putchar('1'); 11 else
12 putchar('0'); 13 if (i == 8) 14 putchar(' '); 15 } 16 putchar('\n'); 17 } 18 int main() 19 { 20 printf("二進制逆序 --- by MoreWindows( http://blog.csdn.net/MoreWindows ) ---\n\n"); 21
22 printf("逆序前: "); 23 unsigned short a = 34520; 24 PrintfBinary(a); 25
26 printf("逆序后: "); 27 a = ((a & 0xAAAA) >> 1) | ((a & 0x5555) << 1); 28 a = ((a & 0xCCCC) >> 2) | ((a & 0x3333) << 2); 29 a = ((a & 0xF0F0) >> 4) | ((a & 0x0F0F) << 4); 30 a = ((a & 0xFF00) >> 8) | ((a & 0x00FF) << 8); 31 PrintfBinary(a); 32 }
2.8 二進制中1的個數
統計二進制中1的個數可以直接移位再判斷,當然像《編程之美》書中用循環移位計數或先打一個表再計算都可以。本文詳細講解一種高效的方法。以34520為例,可以通過下面四步來計算其二進制中1的個數二進制中1的個數。
第一步:每2位為一組,組內高低位相加
10 00 01 10 11 01 10 00
-->01 00 01 01 10 01 01 00
第二步:每4位為一組,組內高低位相加
0100 0101 1001 0100
-->0001 0010 0011 0001
第三步:每8位為一組,組內高低位相加
00010010 00110001
-->00000011 00000100
第四步:每16位為一組,組內高低位相加
00000011 00000100
-->00000000 00000111
這樣最后得到的00000000 00000111即7即34520二進制中1的個數。類似上文中對二進制逆序的做法不難實現第一步的代碼:
x = ((x & 0xAAAA) >> 1) + (x & 0x5555);
好的,有了第一步,后面幾步就請讀者完成下吧,先動動筆再看下面的完整代碼:
1 //二進制中1的個數 by MoreWindows( http://blog.csdn.net/MoreWindows )
2 #include <stdio.h>
3 template <class T>
4 void PrintfBinary(T a) 5 { 6 int i; 7 for (i = sizeof(a) * 8 - 1; i >= 0; --i) 8 { 9 if ((a >> i) & 1) 10 putchar('1'); 11 else
12 putchar('0'); 13 if (i == 8) 14 putchar(' '); 15 } 16 putchar('\n'); 17 } 18 int main() 19 { 20 printf("二進制中1的個數 --- by MoreWindows( http://blog.csdn.net/MoreWindows ) ---\n\n"); 21
22 unsigned short a = 34520; 23 printf("原數 %6d的二進制為: ", a); 24 PrintfBinary(a); 25
26 a = ((a & 0xAAAA) >> 1) + (a & 0x5555); 27 a = ((a & 0xCCCC) >> 2) + (a & 0x3333); 28 a = ((a & 0xF0F0) >> 4) + (a & 0x0F0F); 29 a = ((a & 0xFF00) >> 8) + (a & 0x00FF); 30 printf("計算結果%6d的二進制為: ", a); 31 PrintfBinary(a); 32 return 0; 33 }
2.9 缺失的數字
很多成對出現數字保存在磁盤文件中,注意成對的數字不一定是相鄰的,如2, 3, 4, 3, 4, 2……,由於意外有一個數字消失了,如何盡快的找到是哪個數字消失了?
由於有一個數字消失了,那必定有一個數只出現一次而且其它數字都出現了偶數次。用搜索來做就沒必要了,利用異或運算的兩個特性——1.自己與自己異或結果為0,2.異或滿足交換律。因此我們將這些數字全異或一遍,結果就一定是那個僅出現一個的那個數。 示例代碼如下:
1 copy 2 //缺失的數字 by MoreWindows( http://blog.csdn.net/MoreWindows )
3 #include <stdio.h>
4 int main() 5 { 6 printf("缺失的數字 --- by MoreWindows( http://blog.csdn.net/MoreWindows ) ---\n\n"); 7
8 const int MAXN = 15; 9 int a[MAXN] = {1, 347, 6, 9, 13, 65, 889, 712, 889, 347, 1, 9, 65, 13, 712}; 10 int lostNum = 0; 11 for (int i = 0; i < MAXN; i++) 12 lostNum ^= a[i]; 13 printf("缺失的數字為: %d\n", lostNum); 14 return 0; 15 }
2.10 判斷奇偶數
判斷一個數是基於還是偶數,相信很多人都做過,一般的做法的代碼如下
1 if( n % 2) == 01
2 // n 是個奇數
3 } 4
如果把 n 以二進制的形式展示的話,其實我們只需要判斷最后一個二進制位是 1 還是 0 就行了,如果是 1 的話,代表是奇數,如果是 0 則代表是偶數,所以采用位運算的方式的話,代碼如下:
1 if(n & 1 == 1){ 2 // n 是個奇數。
3 } 4
有人可能會說,我們寫成 n % 2 的形式,編譯器也會自動幫我們優化成位運算啊,這個確實,有些編譯器確實會自動幫我們優化。但是,我們自己能夠采用位運算的形式寫出來,當然更好了。別人看到你的代碼,我靠,牛逼啊。無形中還能裝下逼,是不是。當然,時間效率也快很多,不信你去測試測試。
2.11 3的n次方
如果讓你求解 3 的 n 次方,並且不能使用系統自帶的 pow 函數,你會怎么做呢?這還不簡單,連續讓 n 個 3 相乘就行了,代碼如下:
1 int pow(int n){ 2 int tmp = 1; 3 for(int i = 1; i <= n; i++) { 4 tmp = tmp * 3; 5 } 6 return tmp; 7 }
不過你要是這樣做的話,我只能呵呵,時間復雜度為 O(n) 了,怕是小學生都會!如果讓你用位運算來做,你會怎么做呢?
我舉個例子吧,例如 n = 13,則 n 的二進制表示為 1101, 那么 3 的 13 次方可以拆解為:
3^1101 = 3^0001 * 3^0100 * 3^1000。
我們可以通過 & 1和 >>1 來逐位讀取 1101,為1時將該位代表的乘數累乘到最終結果。直接看代碼吧,反而容易理解:
1 int pow(int n){ 2 int sum = 1; 3 int tmp = 3; 4 while(n != 0){ 5 if(n & 1 == 1){ 6 sum *= tmp; 7 } 8 tmp *= tmp; 9 n = n >> 1; 10 } 11
12 return sum; 13 }
2.12 找出不大於N的最大的2的冪指數
傳統的做法就是讓 1 不斷着乘以 2,代碼如下:
1 int findN(int N){ 2 int sum = 1; 3 while(true){ 4 if(sum * 2 > N){ 5 return sum; 6 } 7 sum = sum * 2; 8 } 9 }
這樣做的話,時間復雜度是 O(logn),那如果改成位運算,該怎么做呢?我剛才說了,如果要弄成位運算的方式,很多時候我們把某個數拆成二進制,然后看看有哪些發現。這里我舉個例子吧。
例如 N = 19,那么轉換成二進制就是 00010011(這里為了方便,我采用8位的二進制來表示)。那么我們要找的數就是,把二進制中最左邊的 1 保留,后面的 1 全部變為 0。即我們的目標數是 00010000。那么如何獲得這個數呢?相應解法如下:
1、找到最左邊的 1,然后把它右邊的所有 0 變成 1
2、把得到的數值加 1,可以得到 00100000即 00011111 + 1 = 00100000。
3、把 得到的 00100000 向右移動一位,即可得到 00010000,即 00100000 >> 1 = 00010000。
那么問題來了,第一步中把最左邊 1 中后面的 0 轉化為 1 該怎么弄呢?我先給出代碼再解釋吧。下面這段代碼就可以把最左邊 1 中后面的 0 全部轉化為 1,
1 n |= n >> 1; 2 n |= n >> 2; 3 n |= n >> 4;
就是通過把 n 右移並且做或運算即可得到。我解釋下吧,我們假設最左邊的 1 處於二進制位中的第 k 位(從左往右數),那么把 n 右移一位之后,那么得到的結果中第 k+1 位也必定為 1,然后把 n 與右移后的結果做或運算,那么得到的結果中第 k 和 第 k + 1 位必定是 1;同樣的道理,再次把 n 右移兩位,那么得到的結果中第 k+2和第 k+3 位必定是 1,然后再次做或運算,那么就能得到第 k, k+1, k+2, k+3 都是 1,如此往復下去....
最終的代碼如下
1 int findN(int n){ 2 n |= n >> 1; 3 n |= n >> 2; 4 n |= n >> 4; 5 n |= n >> 8 // 整型一般是 32 位,上面我是假設 8 位。
6 return (n + 1) >> 1; 7 }
這種做法的時間復雜度近似 O(1)。
2.13 不用+完成加法的算法:
1 int aplusb(int a, int b) { 2 while (b) { 3 int a1 = a ^ b; 4 int b1 = (a & b) << 1; 5 a = a1; 6 b = b1; 7 } 8
9 return a; 10 }
以a=3 (0011), b=5(0101)為例。
a = 0011 => 0110 => 0100 => 0000 =>1000 (return) //未進位加法和
b = 0101 => 0010 =>0100 => 1000 =>0000 //進位
遞歸版本如下:
1 int aplusb(int a, int b) 2 { 3 if (a == 0) return b; 4 if (b == 0) return a; 5 return aplusb((a & b) << 1, a ^ b); 6 }
2.14 計算a要反轉多少位變成b
1 int bitSwapRequired(int a, int b) { 2 int c = a ^ b; 3 int count = 0; 4 while (c) { 5 count++; 6 c &= c - 1; 7 } 8 return count; 9 }
2.15 計算一個32位整數有多少個1
1 int countOnes(int num) { 2 int count = 0; 3 while (num) { 4 count++; 5 num &= num - 1; 6 } 7 return count; 8 }
三、位運算性質
3.1 異或運算滿足以下性質
1. x^x = 0
2. x^y = y^x
3. (x^y)^z = x^(y^z)
4. x^y^y = x
5. 任意正整數i,4*i ^ (4*i + 1) ^ (4*i + 2) ^ (4*i + 3) = 0
四、下面列舉一些常見的二進制位的變換操作
1 功能 示例 位運算 2 去掉最后一位 (101101->10110) x >> 1 3 在最后加一個0 (101101->1011010) x < < 1 4 在最后加一個1 (101101->1011011) x < < 1+1 5 把最后一位變成1 (101100->101101) x | 1 6 把最后一位變成0 (101101->101100) x | 1-1 7 最后一位取反 (101101->101100) x ^ 1 8 把右數第k位變成1 (101001->101101,k=3) x | (1 < < (k-1)) 9 把右數第k位變成0 (101101->101001,k=3) x & ~ (1 < < (k-1)) 10 右數第k位取反 (101001->101101,k=3) x ^ (1 < < (k-1)) 11 取末三位 (1101101->101) x & 7 12 取末k位 (1101101->1101,k=5) x & ((1 < < k)-1) 13 取右數第k位 (1101101->1,k=4) x >> (k-1) & 1 14 把末k位變成1 (101001->101111,k=4) x | (1 < < k-1) 15 末k位取反 (101001->100110,k=4) x ^ (1 < < k-1) 16 把右邊連續的1變成0 (100101111->100100000) x & (x+1) 17 把右起第一個0變成1 (100101111->100111111) x | (x+1) 18 把右邊連續的0變成1 (11011000->11011111) x | (x-1) 19 取右邊連續的1 (100101111->1111) (x ^ (x+1)) >> 1 20 去掉右起第一個1的左邊 (100101000->1000) x & (x ^ (x-1)) 21 判斷奇數 (x&1)==1 22 判斷偶數 (x&1)==0 23 取左右邊的1 x^(x - 1) 24 消去最右邊的1 x&(x - 1)
25 (x - 2) ^ (x - 1) ^ x = x + 1