位元運算簡介及實用技巧(二):進階篇(1)

來源:互聯網
上載者:User

二進位中的1有奇數個還是偶數個
    我們可以用下面的代碼來計算一個32位整數的二進位中1的個數的奇偶性,當輸入資料的二進位表示裡有偶數個數字1時程式輸出0,有奇數個則輸出1。例如,1314520的二進位101000000111011011000中有9個1,則x=1314520時程式輸出1。
var
   i,x,c:longint;
begin
   readln(x);
   c:=0;
   for i:=1 to 32 do
   begin
      c:=c + x and 1;
      x:=x shr 1;
   end;
   writeln( c and 1 );
end.

    但這樣的效率並不高,位元運算的神奇之處還沒有體現出來。
    同樣是判斷二進位中1的個數的奇偶性,下面這段代碼就強了。你能看出這個代碼的原理嗎?
var
   x:longint;
begin
   readln(x);
   x:=x xor (x shr 1);
   x:=x xor (x shr 2);
   x:=x xor (x shr 4);
   x:=x xor (x shr 8);
   x:=x xor (x shr 16);
   writeln(x and 1);
end.

    為了說明上面這段代碼的原理,我們還是拿1314520出來說事。1314520的二進位為101000000111011011000,第一次異或操作的結果如下:

    00000000000101000000111011011000
XOR  0000000000010100000011101101100
---------------------------------------
    00000000000111100000100110110100

    得到的結果是一個新的位元,其中右起第i位上的數表示原數中第i和i+1位上有奇數個1還是偶數個1。比如,最右邊那個0表示原數末兩位有偶數個1,右起第3位上的1就表示原數的這個位置和前一個位置中有奇數個1。對這個數進行第二次異或的結果如下:

    00000000000111100000100110110100
XOR   000000000001111000001001101101
---------------------------------------
    00000000000110011000101111011001

    結果裡的每個1表示原數的該位置及其前面三個位置中共有奇數個1,每個0就表示原數對應的四個位置上共偶數個1。一直做到第五次異或結束後,得到的位元的最末位就表示整個32位元裡有多少個1,這就是我們最終想要的答案。

計算二進位中的1的個數
    同樣假設x是一個32位整數。經過下面五次賦值後,x的值就是原數的二進位表示中數字1的個數。比如,初始時x為1314520(網友抓狂:能不能換一個數啊),那麼最後x就變成了9,它表示1314520的二進位中有9個1。
x := (x and $55555555) + ((x shr 1) and $55555555);
x := (x and $33333333) + ((x shr 2) and $33333333);
x := (x and $0F0F0F0F) + ((x shr 4) and $0F0F0F0F);
x := (x and $00FF00FF) + ((x shr 8) and $00FF00FF);
x := (x and $0000FFFF) + ((x shr 16) and $0000FFFF);

    為了便於解說,我們下面僅說明這個程式是如何對一個8位整數進行處理的。我們拿數字211(我們班某MM的生日)來開刀。211的二進位為11010011。

+---+---+---+---+---+---+---+---+
| 1 | 1 | 0 | 1 | 0 | 0 | 1 | 1 |   <---原數
+---+---+---+---+---+---+---+---+
|  1 0  |  0 1  |  0 0  |  1 0  |   <---第一次運算後
+-------+-------+-------+-------+
|    0 0 1 1    |    0 0 1 0    |   <---第二次運算後
+---------------+---------------+
|        0 0 0 0 0 1 0 1        |   <---第三次運算後,得數為5
+-------------------------------+

    整個程式是一個分治的思想。第一次我們把每相鄰的兩位加起來,得到每兩位裡1的個數,比如前兩位10就表示原數的前兩位有2個1。第二次我們繼續兩兩相加,10+01=11,00+10=10,得到的結果是00110010,它表示原數前4位有3個1,末4位有2個1。最後一次我們把0011和0010加起來,得到的就是整個二進位中1的個數。程式中巧妙地使用取位和右移,比如第二行中$33333333的二進位為00110011001100....,用它和x做and運算就相當於以2為單位間隔取數。shr的作用就是讓加法運算的相同數位對齊。

二分尋找32位整數的前置0個數
    這裡用的C語言,我直接Copy的Hacker's Delight上的代碼。這段代碼寫成C要好看些,寫成Pascal的話會出現很多begin和end,搞得代碼很難看。程式思想是二分尋找,應該很簡單,我就不細說了。
int nlz(unsigned x)
{
   int n;

   if (x == 0) return(32);
   n = 1;
   if ((x >> 16) == 0) {n = n +16; x = x <<16;}
   if ((x >> 24) == 0) {n = n + 8; x = x << 8;}
   if ((x >> 28) == 0) {n = n + 4; x = x << 4;}
   if ((x >> 30) == 0) {n = n + 2; x = x << 2;}
   n = n - (x >> 31);
   return n;
}

只用位元運算來取絕對值
    這是一個非常有趣的問題。大家先自己想想吧,Ctrl+A顯示答案。
    答案:假設x為32位整數,則x xor (not (x shr 31) + 1) + x shr 31的結果是x的絕對值
    x shr 31是二進位的最高位,它用來表示x的符號。如果它為0(x為正),則not (x shr 31) + 1等於$00000000,異或任何數結果都不變;如果最高位為1(x為負),則not (x shr 31) + 1等於$FFFFFFFF,x異或它相當於所有數位取反,異或完後再加一。

高低位交換
    這個題實際上是我出的,做為學校內部NOIp類比賽的第一題。題目是這樣:

    給出一個小於2^32的正整數。這個數可以用一個32位的位元表示(不足32位用0補足)。我們稱這個位元的前16位為“高位”,後16位為“低位”。將它的高低位交換,我們可以得到一個新的數。試問這個新的數是多少(用十進位表示)。
  例如,數1314520用二進位表示為0000 0000 0001 0100 0000 1110 1101 1000(添加了11個前置0補足為32位),其中前16位為高位,即0000 0000 0001 0100;後16位為低位,即0000 1110 1101 1000。將它的高低位進行交換,我們得到了一個新的位元0000 1110 1101 1000 0000 0000 0001 0100。它即是十進位的249036820。

 

    當時幾乎沒有人想到用一句位操作來代替冗長的程式。使用位元運算的話兩句話就完了。
var
   n:dword;
begin
   readln( n );
   writeln( (n shr 16) or (n  shl 16) );
end.

    而事實上,Pascal有一個系統函數swap直接就可以用。

二進位逆序
    下面的程式讀入一個32位整數並輸出它的二進位倒序後所表示的數。
    輸入: 1314520    (二進位為00000000000101000000111011011000)
    輸出: 460335104  (二進位為00011011011100000010100000000000)
var
   x:dword;
begin
   readln(x);
   x := (x and $55555555) shl  1 or (x and $AAAAAAAA) shr  1;
   x := (x and $33333333) shl  2 or (x and $CCCCCCCC) shr  2;
   x := (x and $0F0F0F0F) shl  4 or (x and $F0F0F0F0) shr  4;
   x := (x and $00FF00FF) shl  8 or (x and $FF00FF00) shr  8;
   x := (x and $0000FFFF) shl 16 or (x and $FFFF0000) shr 16;
   writeln(x);
end.

    它的原理和剛才求二進位中1的個數那個例題是大致相同的。程式首先交換每相鄰兩位上的數,以後把互相交換過的數看成一個整體,繼續進行以2位為單位、以4位為單位的左右對換操作。我們再次用8位整數211來示範程式執行過程:
+---+---+---+---+---+---+---+---+
| 1 | 1 | 0 | 1 | 0 | 0 | 1 | 1 |   <---原數
+---+---+---+---+---+---+---+---+
|  1 1  |  1 0  |  0 0  |  1 1  |   <---第一次運算後
+-------+-------+-------+-------+
|    1 0 1 1    |    1 1 0 0    |   <---第二次運算後
+---------------+---------------+
|        1 1 0 0 1 0 1 1        |   <---第三次運算後
+-------------------------------+

Copyright也很強
writeln('Matrix' , 42 XOR 105 , '原創,轉貼請註明出處');

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.