位運算之美——用+,-和位運算實現正整數除法和取模(一)
9月21日,對本文從格式到部分內容上都進行了修改
另外,鑒于某些轉載沒有注明出處,考慮到版權問題,特聲明如下:
作者:翼帆@cppblog
原文地址:http://www.shnenglu.com/xiaoyisnail/archive/2009/09/19/96707.html
本文版權歸作者和cppblog共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。
今天看了一位師兄去年的筆經總結,其中有一題是“不許用%和/來實現求任意數除以3的余數”,我想考官的目的應該是想考察學生對位運算的熟悉程度吧,于是我把題目擴展成“只能用+,-和位運算實現正整數除法(/)和取模(%)”,注意:這里不能使用其它的庫例程來輔助計算,如log,log10等。在思考這道題目的過程中,我又涉及到了許多二進制相關的題目,如:
判斷給定的整數是不是2的整數次冪
判斷給定的整數是不是4的整數次冪
求給定整數的二進制表示中1的個數
求給定整數的二進制表示中0的個數
求給定整數的二進制表示中最高位1的位置
求大于等于給定整數的最小的2的整數次冪
求給定整數的二進制表示的有效位數
...
9月21日補充:這里只考慮值為正整數的情況。
這些題目都是經典老題,頻繁出現于各類筆試面試題中,除了能考察位運算外,還能考察應聘者能否給出創新的算法來更好地解決問題。可以說這些題目都不難,如果使用32位的int來表示整數的話,蠻力法都可以比較好地完成任務,但是如果想盡可能地提高效率,那就需要動一番腦經了。下面給出我對這些問題的整理和C++實現,并在下次的文章中給出只用+,-和位運算實現的正整數除法和取模。
從某種意義上講,特別是從充分利用底層硬件的計算能力(利用特殊的cpu指令)來看,這些解法肯定不是最優的,所以還希望大俠們多多指點。
判斷給定的整數是不是2的整數次冪
這應該是最簡單的,利用最高位是1,其后所有位為0的特性,常數時間解決問題:
2 inline bool is_2exp(unsigned int n)
3 {
4 return !(n&(n-1));
5 }
求給定整數的二進制表示中1的個數
考慮到n-1會把n的二進制表示中最低位的1置0并把其后的所有0置1,同時不改變此位置前的所有位,那么n&(n-1)即可消除這個最低位的1。這樣便有了比順序枚舉所有位更快的算法:循環消除最低位的1,循環次數即所求1的個數。此算法的時間復雜度為O(n的二進制表示中的1的個數),最壞情況下的復雜度O(n的二進制表示的總位數)。

2

3

4

5

6

7

8

9

10

11


2

3

4

5

6

7

8

9

10

11

12


2

3

4

5

6

7

8

9

10

11

12

13


2

3

4

5

6

7

8

9

10

11

12

求給定整數的二進制表示的有效位數
上面提到了線性地求這個位數(下文記為m),即“循環右移1位,記錄右移次數”,時間復雜度O(m)。但是我想,一看到這個題目,所有人的第一反應應該是floor(log2(n))+1吧,但是請注意,本文在一開始就規定了“不能使用庫例程”,那么在這個限制下該怎么做呢?有沒有比線性時間更好的算法呢?其實到目前為止我也沒有什么特別好的算法,希望誰有什么精妙的算法能指點一下,不要打我。。。

2

3

4

5

6

7

8

9

10

11

求大于等于給定整數的最小的2的整數次冪
首先是最簡單的思路:求出n的二進制表示的總位數m,于是1<<m即為所求值,當然這里要排除n自身就是2的整數次冪的情況,復雜度O(m),實現如下:

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

那有沒有不用計算位數m,從而效率更好的算法呢,能不能像在計算二進制表示中1的個數時那樣根據1的個數來設計算法呢?回到那一題中,“n-1會把n的二進制表示中最低位的1置0并把其后的所有0置1”,那么n|=n-1就把n的二進制表示中最低位1后的所有0置1,再加上1,那么就把最低位1左移了一位。于是,便有了更好的算法:循環左移最低位的1,直到n是2的整數次冪。該算法跟二進制表示中的1的個數和位置有關,最壞時間復雜度還是O(二進制表示位數),但是比起上一個實現,這個算法在多數情況下都比上一個算法快。實現如下:

2

3

4

5

6

7

8

9

10

11

12

13

14

15

最后來一個簡單的擴充題目:
判斷給定的整數是不是4的整數次冪
觀察4的整數次冪的特征,容易發現除了滿足n&(n-1)==0外,唯一的1位后的0的個數是偶數,這從4x=22k也能簡單地得到。這就很直觀地衍生出一個簡單的算法:

2

3

4

5

6

7

8

9

10

11


說明:寫這篇文章,已經三次丟失全文了,把我快搞瘋了,firefox下好像有點問題,先把文章發上來,過會兒換到IE下繼續。。。
再說明:換了IE后就沒再出問題了,不過寫著寫著發現寫了好久,先歇會兒,得看書補習功課了

最后的說明:下次會給出本文最初提出的問題(只能用+,-和位運算實現正整數除法(/)和取模(%))的實現。
posted on 2009-09-19 13:58 翼帆 閱讀(8295) 評論(3) 編輯 收藏 引用 所屬分類: 算法