二進位中的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 , '原創,轉貼請註明出處');