本文是准備找工作過程中關於位運算的一些積累和記錄的整理。注意:部分位運算的處理結果依賴於變量所屬類型的字長,使用時請結合具體環境修改。
1.XOR應用
性質:滿足交換率、結合律,一個數與其自身異或結果為0。
(1)不用中間變量,交換兩數
a = a^b; b = b^a; //b = b^(a^b),thus b becomes the earlier a a = a^b; //a = (a^b)^a,thus a becomes the earlier b
擴展:不用異或,同樣也能不用中間變量,交換兩數
a = a - b; b = a + b; // b = (a - b)+ b, thus b becomes the earlier a a = b - a; // a = a - (a - b), thus a becomes the earlier b
但是這種方式引入了一個陷阱,如果a是一個很大的正數而b是一個很小的負數,那么a-b就會溢出。雖然在b=a+b時可能會通過再一次溢出從而獲得真實的a的值,不推薦這種利用未定義行為的解法。
如何理解這種解法?其實第一行是a=a-b還是a=a+b再或者是a=a*b都可以,對應地在第二行把b通過這個式子和b本身的運算求出a即可,再在第三行利用ab的組合值以及原先的a求解b。明顯地,使用*比+或-更容易溢出。理解后,完全不必死記硬背這三個式子,看成是解方程就不難了。
(2)尋找只出現1次的一個數,其他數出現偶數次(或尋找唯一一個出現奇數次的數,其他數出現偶數次)
解法:全部數做XOR,最后的結果就是要找的數。
擴展:尋找出現奇數次的數,其他不必尋找的數只出現偶數次。
常見的面試題擴展,思路還是原來的思路,先全部XOR一遍,在獲得的結果上,對每一位為1(即可能有兩個不同的數,二進制標識中該位不同)進行分組,構造出所有待找出的數。
這么概括很抽象,看一道具體的筆試題吧,通過解題就容易理解了。
(小米2013校招筆試題)一個數組里,除了三個數是唯一出現的,其余的都出現偶數個,找出這三個數中的任一個。比如數組元素為【1, 2,4,5,6,4,2】,只有1,5,6這三個數字是唯一出現的,我們只需要輸出1,5,6中的一個就行。
解答:http://blog.csdn.net/leo115/article/details/8036990
(3)NIM游戲的狀態分析
請參考《編程之美》1.12 NIM(2) “拈”游戲分析。其核心是,兩種完全不同的狀態(安全狀態和不安全狀態)的XOR值恰為0和1。
2.加法,不用+-*/做加法(《劍指Offer》面試題47)
迭代版本(《劍指Offer》面試題47)
int Add(int num1,int num2) { int sum,carry; do { sum = num1^num2; carry= (num1 & num2)<<1; num1 = sum; num2 = carry; } while (num2!=0) return num1; }
遞歸版本(CareerCup 20.1)
int add_no_arithm(int num1,int num2) { if(num2 == 0) return num1; int sum = a^ b; int carry = (a&b)<<1; return add_no_arithm(sum,carry); }
3.求兩數的平均數 ,不用-、*、/求兩數的平均數
似乎是出自《程序員面試寶典》,但是我在第三版第12章沒找到原題。用下面的代碼就能“神奇地”獲得兩個整型的平均值
int average(int x,int y) { return ( (x&y) + ( (x^y)>>1 ) ); }
解釋請看:http://blog.csdn.net/leo115/article/details/7993110,不過也是轉載,原出處疑似已失效。
4.不用*和/做除法(《算法設計手冊》面試題1-28)
慢速版本和優化版本請參考舊作:http://www.cnblogs.com/wuyuegb2312/p/3257558.html
縱觀第2、3、4條可以發現,如果問題限制不允許使用某種四則運算符以及%,就可以在位運算上打主意了。
5.二進制中1的個數
不要覺得很trick,這是K&R提到過的。值得注意的是,如果使用C實現,為了避免實現定義不同造成的結果不同,需要把該變量轉化為無符號型。
int bitcount(unsigned x) { int b; for(b=0;x|=0;x>>=1) if(x&01) b++; return b; }
事實上K&R習題2-9提到了一種更快的算法:
int bitcount(unsigned x) { int b; for(b=0;x!=0;x&= x-1) b++; return b; }
6.從無符號型x的第p位開始,取n位(K&R)
//最低位是第0位 unsigned getbits(unsigned x,int p, int n) { return (x>>(p+1-n)) & ~(~0<<n); }
7.利用同余的性質和位運算加速的輾轉相減求最大公約數法(《C語言參考手冊》第七章)
unsigned binary_gcd(unsigned x, unsigned y) { unsigned temp; unsigned common_power_of_two = 0; if(x==0) return 0; if(y==0) return 0; /*find the largest power of two that divides both x and y*/ while(((x|y)&1)==0) { x >>= 1; y >>= 1; ++common_power_of_two; } while((x &1) == 0) x >>= 1; while(y) { /*x is odd and y is nonzero here*/ while((y&1)==0) y >>= 1; /*x and y are odd here*/ temp = y; if (x>y) y = x - y; else y = y-x; x = temp; /*Now x has the old value of y,which is odd. y is even,because it is the difference of two odd numbers therefore it will be right-shifted at least once on the next iteration.*/ } return (x<<common_power_of_two); }
8.不用大於小於號,求兩數較大值(CareerCup 19.4)
int getMax(int a,int b) { int c = a - b; int k = (c>>31)&0x1; int max = a-k*c; return max; }
在不允許使用>、<以及任何判斷語句時,使用移位判斷一個數的正負性是常用技巧,如《深入理解計算機系統》2.3.7節習題2.42中求補碼對給定2的冪的商的解法。這個問題以及求解涉及到補碼的表示和運算的知識,在此不進行詳述,有興趣的讀者可以自行查閱。
更多關於《深入理解計算機系統》習題答案的內容,可以參考:http://blog.csdn.net/yang_f_k
9.實現位向量
這種做法是對空間的高效利用。對《編程珠璣》上位向量實現全面分析的舊作一篇:http://www.cnblogs.com/wuyuegb2312/p/3136831.html
10.其他
附上MoreWindows前輩的一篇博文鏈接:位操作基礎篇之位操作全面總結,順便把該文的目錄拿來做個索引: