玩轉位運算的N條實踐小技巧

寫在前面

如果覺得本篇文章有所幫助,記得點個關注和點個贊哦,非常感謝支持。
對於位運算我們應該不陌生(當然,如果你還很陌生,你可以看我之前寫過的關於位運算的文章,挺詳細的),這一篇主要是針對平時我們日常生活中,碰到的關於位運算的一些問題,而總結出來的一些實踐技巧,能夠幫助我們快速解決一下相關問題,有助於玩轉位運算的操作。本文會不斷更新,日後碰到很多奇思妙想會繼續更新進來,也歡迎大家留言更新。

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,如果 AB 在第i0<=i<32)個位上相等,則不需要改變這個BIT位,如果在第 i 位上不相等,則需要改變這個BIT位。所以問題轉化爲了A和B有多少個BIT位不相同。聯想到位運算有一個異或操作,相同爲 0,相異爲 1,所以問題轉變成了計算A異或B之後這個數中 1 的個數

  • 去考慮這樣一個問題,一個數大於一個數意味着什麼?或者說,怎麼判斷一個數大於一個數?

不管在十進制還是二進制,都存在這樣一個事實 ,我們只需要從高位向低位看去,直到某一位不相同,大小也就判斷了出來。十進制中,比如 6489...6486...,由於 9 > 6,所以不管後邊的位是什麼 6489... 一定會大於 6486... 。對於二進制,一樣的道理,但因爲二進制只有兩個數 01,所以當出現某一位大於另一位的時候,一定是 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倍數補全,當 n2 的冪時,(x + n - 1) & ~(n - 1) 會找到第一個大於 x 的數,且它正好是 n 的整數倍。
  • 計算 n+1n-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))
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章