關於位運算

2021-06-27 19:43:18 字數 3327 閱讀 5281

9月21日,對本文從格式到部分內容上都進行了修改

今天看了一位師兄去年的筆經總結,其中有一題是「不許用%和/來實現求任意數除以3的餘數」,我想考官的目的應該是想考察學生對位運算的熟悉程度吧,於是我把題目擴充套件成「只能用+,-和位運算實現正整數除法(/)和取模(%)」,注意:這裡不能使用其它的庫例程來輔助計算,如log,log10等。在思考這道題目的過程中,我又涉及到了許多二進位制相關的題目,如:

判斷給定的整數是不是2的整數次冪

判斷給定的整數是不是4的整數次冪

求給定整數的二進位制表示中1的個數

求給定整數的二進位制表示中0的個數

求給定整數的二進位制表示中最高位1的位置

求大於等於給定整數的最小的2的整數次冪

求給定整數的二進位制表示的有效位數

...9月21日補充:這裡只考慮值為正整數的情況。

這些題目都是經典老題,頻繁出現於各類筆試面試題中,除了能考察位運算外,還能考察應聘者能否給出創新的演算法來更好地解決問題。可以說這些題目都不難,如果使用32位的int來表示整數的話,蠻力法都可以比較好地完成任務,但是如果想盡可能地提高效率,那就需要動一番腦經了。下面給出我對這些問題的整理和c++實現,並在下次的文章中給出只用+,-和位運算實現的正整數除法和取模。

從某種意義上講,特別是從充分利用底層硬體的計算能力(利用特殊的cpu指令)來看,這些解法肯定不是最優的,所以還希望大俠們多多指點。

判斷給定的整數是不是2的整數次冪

這應該是最簡單的,利用最高位是1,其後所有位為0的特性,常數時間解決問題:

1 //判斷n是否是2的正整數冪

2 inline bool is_2exp(unsigned int n)

3 求給定整數的二進位制表示中1的個數

考慮到n-1會把n的二進位制表示中最低位的1置0並把其後的所有0置1,同時不改變此位置前的所有位,那麼n&(n-1)即可消除這個最低位的1。這樣便有了比順序列舉所有位更快的演算法:迴圈消除最低位的1,迴圈次數即所求1的個數。此演算法的時間複雜度為o(n的二進位制表示中的1的個數),最壞情況下的複雜度o(n的二進位制表示的總位數)。

1//計算n的二進位制表示中1的個數

2inline int count1(unsigned int n)

310 return r;

11} 既然有了求給定整數的二進位制表示中1的個數的辦法,那麼想要求給定整數的二進位制表示中0的個數就很簡單了。事實上,在二進位制中,完全可以把0和1看作是對稱的兩個物件,取反操作(~)可以任意的切換這兩個物件,只要先對n進行一次取反,然後再用上述演算法即能得到二進位制表示中0的個數。首先看下面的**:

1//計算n的二進位制表示中0的個數

2inline int count0_wrong(unsigned int n)

311 return r;

12} 不知大家有沒有看出問題來?是的,~操作符會把所有高位的都取反,而不是只把有效位取反,所以我們需要乙個能保持高位不變的位取反操作,下面是我的實現,時間複雜度和求二進位制表示中1的個數的演算法相同,都與二進位制表示中1的個數有關:

1//保持高位取反

2inline unsigned int negate_bits(unsigned int n)

31112 return r;

13} 有了這個特殊的取反操作,求給定整數的二進位制表示中0的個數的辦法就簡單了:

1//計算n的二進位制表示中0的個數

2inline int count0( unsigned int n)

311 return r;

12} 看到這裡,聰明的讀者肯定看出問題來了,其實我幹了一件很蠢的事情。看看上述演算法的時間複雜度,negate_bits花了o(n的二進位制表示中1的個數),while迴圈計算取反後的n的二進位制表示中1的個數,事實上就是o(n的二進位制表示中0的個數),兩部分加起來其實就是二進位制表示總的有效位數,換句話說,這個演算法是線性的,而事實上,我們完全可以先線性地求出這個總的有效位數,然後減去1的位數,即得到0的位數,根本不用費那麼大勁去整個保持高位的取反操作,兩者的時間複雜度在漸進意義上也是相同的。所以,我犯傻了,但是這裡又引出另乙個問題:

求給定整數的二進位制表示的有效位數

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

1//求給定整數的二進位制表示的位數,線性演算法

2int count_bit(unsigned int n)

310 return r;

11} 求大於等於給定整數的最小的2的整數次冪

首先是最簡單的思路:求出n的二進位制表示的總位數m,於是1<>= 1;

12 r <<= 1;

13 }

1415 return r;

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(二進位制表示位數),但是比起上乙個實現,這個演算法在多數情況下都比上乙個演算法快。實現如下:

1//求大於等於n的最小的2的正整數冪,方法2

2//計算時間與n的二進位制表示中1的個數和位置有關,比方法1效率高

3//最壞情況下的時間複雜度與方法1相同

4unsigned int high_2exp_2(unsigned int n)

51314 return n;

15}

最後來乙個簡單的擴充題目:

判斷給定的整數是不是4的整數次冪

觀察4的整數次冪的特徵,容易發現除了滿足n&(n-1)==0外,唯一的1位後的0的個數是偶數,這從4x=22k也能簡單地得到。這就很直觀地衍生出乙個簡單的演算法:

1//判斷n是否是4的整數次冪

2bool is_4exp(unsigned int n)

3 演算法很直觀,但是比起is_2exp的常數時間is_4exp的線性時間總讓我覺得不能接受,不過無奈還是沒有想出好辦法來,哎。。。求大牛指點啊

再說明:換了ie後就沒再出問題了,不過寫著寫著發現寫了好久,先歇會兒,得看書補習功課了

關於位運算

今天看k神的一篇題解,看到一些關於位運算的技巧。又想起以前學長川哥給的一篇位運算對集合處理的技巧的pdf。所以就一起整理一下。各種位運算子的功能就不累述了。集合的位運算表示 來自川哥給的pdf 基本表示 1.空集 0 2.全集 1 3.只含有第 i 個元素的集合1 集合間的操作 1.s 與 t 的並...

關於 位運算

位運算 位運算是把數字用二進位制表示之後,對每一位上0或者1的運算。理解位運算的第一步是理解二進位制。二進位制是指數字的每一位都是0或者1.比如十進位制的2轉化為二進位制之後就是10。在程式設計師的圈子裡有乙個流傳了很久的笑話,說世界上有10種人,一種人知道二進位制,而另一種人不知道二進位制。其實二...

關於位運算

主要 1.如果兩個相應的二進位制位都為 則該位的結果值為1 否則為0。是二進位制 與 運算,參加運算的兩個數的二進位制按位進行運算,運算的規律是 0 0 0 0 1 0 1 0 0 1 1 1 對於參加運算的數要換算為二進位制進行運算,例如3 2的結果是2,過程如下 3 2 0111 0010 00...