【位運算經典應用】 求二進制逆序


本文我們來講講二進制的經典應用-求二進制的逆序。本文的重點除了算法本身外,還有<<>>>的神奇應用。

leetcode中有道具體的題目-Reverse Bits,題目很簡單,給你一個32位無符號整數,比如43261596(00000010100101000001111010011100),返回964176192(00111001011110000010100101000000)。

常規解法###


因為題目要求中不去除前導0,所以toString()方法英雄無用武之地了,常規解法只能一位位枚舉。我們從低到高位枚舉n的二進制碼,然后將低位提到高位來表示逆序后的二進制碼。

代碼很簡單:

var reverseBits = function(n) {
  var ans = 0;
  for(var i = 31; i >= 0; i--) {
    ans |= (n & 1) << i;
    n >>= 1;
  }
  return ans >>> 0;
};

前面都很好理解,return ans >>> 0可能會引起你的注意,這也正是文章開頭所說的>>>的神奇應用。

假設我們把n=1帶入程序,將n用二進制碼表示:

0000 0000 0000 0000 0000 0000 0000 0001

for循環運行完后,得到ans的二進制碼為:

1000 0000 0000 0000 0000 0000 0000 0000

但是對於JavaScript而言,它的32位二進制碼的第一位總是符號位,所以上面的二進制碼所表示的數是-2147483648。現在問題來了,在JavaScript如何把一個signed的32位整數轉成一個unsigned的32位整數? 答案就是>>>0。於是程序也就很好理解了。

var a = -2147483648; // 1000 0000 0000 0000 0000 0000 0000 0000 signed
console.log(a >>> 0); // 2147483648 unsigned

位運算解法###


我們以12345為例(假設16位無符號整數,32位類似):

0011000000111001

將它逆序后得到新的二進制碼:

1001110000001100

轉換為十進制后,為39948。

第一步:每2個為一組,組內高低位交換

00 11 00 00 00 11 01 10

第二步:每4個為一組,組內高低位交換

1100 0000 1100 1001

第三步:每8個為一組,組內高低位交換

00001100 10011100

第四步:每16個為一組,組內高低位交換

10001110000001100

那么如何用程序來表示呢?譬如第一步中,我們如何快速從0011000000111001轉為00 11 00 00 00 11 01 10呢?

將奇數位提取出來:

0_1_0_0_0_1_1_0_ 

空隙中用0填補(這里為了視覺效果,用了字母):

0a1a0a0a0a1a1a0a

將偶數位同樣提取:

_0_1_0_0_0_1_0_1

空隙處用0填補(這里為了視覺效果,用了字母):

a0a1a0a0a0a1a0a1

將奇數位右移一位,再將偶數位左移一位,相加,即得到結果:

00a1a0a0a0a1a1a0
0a1a0a0a0a1a0a10
0011000000110110

取x的奇數位並將偶數位用0填充代碼實現就是 x & 0xAAAA(1010101010101010),取x的偶數位並將奇數位用0填充代碼實現就是 x & 0x5555(0101010101010101)

因此第一步用代碼實現就是:

x = ((x & 0xAAAA) >> 1) | ((x & 0x5555) << 1);

之后的代碼實現也是類似,我們用C++來解Reverse Bits

class Solution {
  public:
    uint32_t reverseBits(uint32_t n) {
      n = ((n & 0xAAAAAAAA) >> 1) | ((n & 0x55555555) << 1);
      n = ((n & 0xCCCCCCCC) >> 2) | ((n & 0x33333333) << 2);
      n = ((n & 0xF0F0F0F0) >> 4) | ((n & 0x0F0F0F0F) << 4);
      n = ((n & 0xFF00FF00) >> 8) | ((n & 0x00FF00FF) << 8);
      n = ((n & 0xFFFF0000) >> 16) | ((n & 0x0000FFFF) << 16);
      return n;
    }
};

高級語言或者說有unsigned int32的語言,這樣也就可以了。但是蛋疼的是JavaScript是沒有的,我們還是可以用>>>來進行運算,從而達到操作符號位的效果。

完整代碼:

var reverseBits = function(n) {
  n = ((n & 0xAAAAAAAA) >>> 1) | ((n & 0x55555555) << 1);
  n = ((n & 0xCCCCCCCC) >>> 2) | ((n & 0x33333333) << 2);
  n = ((n & 0xF0F0F0F0) >>> 4) | ((n & 0x0F0F0F0F) << 4);
  n = ((n & 0xFF00FF00) >>> 8) | ((n & 0x00FF00FF) << 8);
  n = ((n & 0xFFFF0000) >>> 16) | ((n & 0x0000FFFF) << 16);

  return n >>> 0;
};

效率之高,看圖說話。


最后再次總結下,JavaScript雖然沒有unsigned int32,雖然位運算過程中第一位始終是符號位,但是並不代表JavaScript不能操縱unsigned int32的整數,因為有如下兩大利器能實現unsigned32位整數和signed32位整數之間的轉換:

var a = -2147483648; // 1000 0000 0000 0000 0000 0000 0000 0000 signed
console.log(a >>> 0); // 2147483648 unsigned

var a = 4294967295; // 1111 1111 1111 1111 1111 1111 1111 1111 unsigned
console.log(a << 0); // -1 signed

當然具體情境還要具體分析。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM