寫在前面
如果覺得本篇文章有所幫助,記得點個關注和點個贊哦,非常感謝支持。
對於位運算我們應該不陌生(當然,如果你還很陌生,你可以看我之前寫過的關於位運算的文章,挺詳細的),這一篇主要是針對平時我們日常生活中,碰到的關於位運算的一些問題,而總結出來的一些實踐技巧,能夠幫助我們快速解決一下相關問題,有助於玩轉位運算的操作。本文會不斷更新,日後碰到很多奇思妙想會繼續更新進來,也歡迎大家留言更新。
N條實踐技巧
- 在位運算中,我們如果想要將二進制位中最右邊的1置爲0,我們只需要
n & (n-1)
這一點其實不難理解,我們知道,在二進制中,n - 1
會導致 n
最右邊的 1
要退 1
,就像我們在十進制中滿十進一,少十退一。然後此時用 n & (n-1)
進行運算,就可以成功的去掉最右邊的 1
-
計算一個整數二進制中1的個數
因爲1可以不斷的通過x&(x-1)
這個操作消去,所以當最後的值變成0
的時候,也就求出了二進制中1
的個數 -
如果將整數A轉換成整數B,需要改變多少個比特位
思考將整數A
轉換爲B
,如果A
和B
在第i
(0<=i<32
)個位上相等,則不需要改變這個BIT位,如果在第i
位上不相等,則需要改變這個BIT位。所以問題轉化爲了A和B有多少個BIT位不相同。聯想到位運算有一個異或操作,相同爲0
,相異爲1
,所以問題轉變成了計算A異或B之後這個數中1
的個數 -
去考慮這樣一個問題,一個數大於一個數意味着什麼?或者說,怎麼判斷一個數大於一個數?
不管在十進制還是二進制,都存在這樣一個事實 ,我們只需要從高位向低位看去,直到某一位不相同,大小也就判斷了出來。十進制中,比如 6489...
和 6486...
,由於 9 > 6
,所以不管後邊的位是什麼 6489...
一定會大於 6486...
。對於二進制,一樣的道理,但因爲二進制只有兩個數 0
和 1
,所以當出現某一位大於另一位的時候,一定是 1 > 0
。更形象一點如下,
m -> S S S 0 X X X X
n -> S S S 1 X X X X
前邊的若干位都相同,然後從某一位開始從 0
變成 1
。通過這個認識,我們有時候可以配合移位操作,完成許多的巧妙思路。
-
判斷該數是不是二的次方數,我們只需要對
(n & (n - 1))
進行運算,如果結果爲0,那麼就是二的次方數 -
判斷一個數的奇偶性,我們只需要對
i & 1
進行運算,如果結果爲1
則爲偶數,反之否則爲奇數 -
兩個相同的數進行異或(^)爲0
我們可以利用異或的這個特性來消除重複的數, 例如找出一個數組中落單的數(數量爲奇數的某個數), 相反我們也可以利用這個特性來找出數組中唯一成對的數。
- 利用異或實現數的交換
a = a ^ b;
b = a ^ b;
a = a ^ b;
- 利用位移實現乘/除2的冪次倍,即一個數向右移1位就相當於除以2, 右移2位相當於除以4,向左移就是乘法,規律同除。
int a = 3>>1; // a =1,注意此除法是整除, 不保留小數位
int a = 3<<1; // a = 6
int a = 3<<2; // a = 12
- 對2的n次方取餘,可以使用
m & (n - 1)
運算,因爲,如果是2
的冪,n
一定是000100...
,n-1就是000011....
,所以做與運算結果保留m在n範圍的非0的位 - 我們可以通過
~n + 1
來獲得n
的相反數,當然,也可以寫成這樣(n ^ -1) + 1
- 如果碰到
if(x == a) x = b
或者if(x == b) x = a
可以通過以下這樣代替x = a ^ b ^ x
- n倍數補全,當
n
爲2
的冪時,(x + n - 1) & ~(n - 1)
會找到第一個大於x
的數,且它正好是n
的整數倍。 - 計算
n+1
與n-1
,-~n == n + 1
,~n
爲其取反,負號 ’ - ’ 再對其取反並加1
。~-n == n - 1
,思路就是找到最低位的第一個1
,對其取反並把該位後的所有位也取反,即01001000
變爲01000111
。 - 判斷二進制中1的奇偶性
x = x ^ (x >> 1);
x = x ^ (x >> 2);
x = x ^ (x >> 4);
x = x ^ (x >> 8);
x = x ^ (x >> 16);
cout << (x & 1) << endl; // 輸出 1 爲奇數
以十進制數1314520爲例,其二進制爲0001 0100 0000 1110 1101 1000。第一次異或操作的結果如下:
0001 0100 0000 1110 1101 1000
^ 0000 1010 0000 0111 0110 1100
= 0001 1110 0000 1001 1011 0100
得到的結果是一個新的二進制數,其中右起第i位上的數表示原數中第i和i+1位上有奇數個1還是偶數個1。比如,最右邊那個0表示原數末兩位有偶數個1,右起第3位上的1就表示原數的這個位置和前一個位置中有奇數個1。對這個數進行第二次異或的結果如下:
0001 1110 0000 1001 1011 0100
^ 0000 0111 1000 0010 0110 1101
= 0001 1001 1000 1011 1101 1001
結果裏的每個1表示原數的該位置及其前面三個位置中共有奇數個1,每個0就表示原數對應的四個位置上共偶數個1。
一直做到第五次異或結束後,得到的二進制數的最末位就表示整個32位數裏1的奇偶性。
- 取出最右側的1
int quyu(int pos){
return pos & (~pos + 1);
}
表格
功能 | 示例 | 位運算 |
---|---|---|
去掉最後一位 | (101101->10110) | x >> 1 |
在最後加一個0 | (101101->1011010) | x << 1 |
在最後加一個1 | (101101->1011011) | x << 1+1 |
把最後一位變成1 | (101100->101101) | x | 1 |
把最後一位變成0 | (101101->101100) | x | 1-1 |
最後一位取反 | (101101->101100) | x ^ 1 |
把右數第k位變成1 | (101001->101101,k=3) | x |
把右數第k位變成0 | (101101->101001,k=3) | x & ~(1 << (k-1)) |
右數第k位取反 | (101001->101101,k=3) | x ^ (1 << (k-1)) |
取末三位 | (1101101->101) | x & 7 |
取末k位 | (1101101->1101,k=5) | x & (1 << k-1) |
取右數第k位 | (1101101->1,k=4) | x >> (k-1) & 1 |
把末k位變成1 | (101001->101111,k=4) | x | (1 << k-1) |
末k位取反 | (101001->100110,k=4) | x ^ (1 << k-1) |
把右邊連續的1變成0 | (100101111->100100000) | x & (x+1) |
把右起第一個0變成1 | (100101111->100111111) | x | (x+1) |
把右邊連續的0變成1 | (11011000->11011111) | x | (x-1) |
取右邊連續的1 | (100101111->1111) | (x ^ (x+1)) >> 1 |
去掉右起第一個1的左邊 | (100101000->1000) | x ^ (x ^ (x-1)) |