運算可謂是與編程息息相關,咱們編寫的每個程序可能都帶有加減乘除,固然這是最基礎的運算了。在大一下的時候學了第一門編程語言C,隨着也學到了取餘(%)和三目運算符(? :),當時就以爲(? :)真的牛逼,但在編程時卻不多用到,由於if和else已經刻在個人腦子裏。python
不一樣語言中的運算符也會有一些誤差,像Python中的整除(//)是C中沒有的,C中的三目運算符在Python中也有着不一樣的表現形式,好比np.where和if、else組合。算法
下面介紹我的認爲比較高大上的位運算符,說它高大上很大一方面是由於位運算用來解決某些問題十分便捷,而另外一小方面就是由於它能夠ZhuangBi。若是你沒接觸過位運算,即便是不多一部分關於位運算的代碼,你也會一臉懵逼,而在你瞭解位運算後,你會發現這種方法確實比較巧妙。編程
咱們知道全部數字包括字母、符號等在計算機中都是以二進制形式存儲的,而位運算就是直接對二進制進行操做,常見的位運算包括如下幾種:數組
這些位運算符號按照優先級順序排序以下:編程語言
1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|
~ | <<、>> | & | ^ | 按位或 |
爲了便於理解和觀看,下面舉例中只列舉8位的二進制
按位與運算法則能夠歸納成「同真則真,反之則假」,在0和1之間的運算,有如下形式:學習
好比數字5和數字6之間採用按位與運算,將兩個數組8位二進制形式的每一位對應,運用上面的法則就能夠得出一個新的數字4,即5 & 6 = 4。spa
按位或運算法則能夠歸納成「同假才假,反之則真」,在0和1之間的運算,有如下形式:3d
一樣還用數字5和數字6舉例,利用上述相同方式在兩者之間作按位或運算,能夠獲得一個新的數字7,即5 | 6 = 7。code
按位異或運算法則能夠歸納成「相同則真,不一樣則假」,在0和1之間的運算,有如下形式:blog
仍然仍是數字5與數字6爲例利用上述相同方式在兩者之間作按位異域運算,能夠獲得一個新的數字3,即5 ^ 6 = 3。
左移操做就是將一個數字的二進制形式總體向左移動,而後右邊的虛位補0,具體移動看下圖:
這個例子是2<<2 = 8的體現,能夠看到1本來處於右邊第2位,若是總體向左移動兩位,那麼1就移動到了右邊第4位,空下的兩位用0補上便可。
右移操做與左移操做方式相同,方向相反,具體移動看下圖:
這個例子是6>>2 = 1的體現,只須要總體向右移動兩位,而後左邊虛位補0,不須要顧慮會不會有1被覆蓋。
這兩個符號比較容易混亂,這裏給一個本身的小Tips:箭頭朝哪向哪移,左移值增大,左移值減少。
取反操做就比較簡單了,只須要記住這樣一個小式子: ~x = -(x+1),好比數字5取反操做就等於-6,即~5 = -(5+1) = -6。
已經介紹完了這六個位運算符是如何對二進制進行操做的,但是簡單的介紹並不能體現出位運算的高大上,下面利用位運算的技巧解決一些問題,這些問題並非很難,可是咱們從中能夠認識到位運算的便捷,以及加深對位運算操做的印象。
對於這個問題可能咱們想到的第一個方法就是利用第三個變量來幫助交換,好比若要交換a、b兩個變量的值,就須要定義一個臨時變量temp輔助。
a = 1;b = 2 temp = 0 temp = a; a = b; b = temp
這樣作是須要額外存儲空間的,那有沒有一種方法僅在原地就能夠交換a、b兩個變量的值呢?位運算就能夠,並且十分簡單。
a = a^b #(1) b = a^b #(2) a = a^b #(3)
若是你沒接觸過位運算,對於這三行代碼確定很懵,讀懂這三行代碼你還須要知道按位異或的幾個性質:
若將式子(1)代入式子(2),利用上述性質並結合按位異或「相同則假」的法則,能夠將式子(2)變成如下形式:
b = a^b^b = a^0 = a
這樣a的值就賦給b了,若是再將求出的式子(2)代入式子(3),式子(3)則變成:
a = a^a^b = 0^b = b
這樣就完成了變量a、b之間的調換,是否是已經有NiuBi的感受了,繼續看下一道。
假設給定一個數字,你須要判斷它是否爲2的冪,若是是,須要返回true,反之就返回false。
這個問題利用循環方法也比較不錯,設定一個循環條件,而後讓輸入整數n一直除以2,最後只須要判斷n是否等於1便可,由於n==1只有當n爲2的冪時才知足。
while n>1: n/=2 return n==1
這種方法應該算是一種很普通的方法了,體現不出本身的逼格怎麼辦?看看利用位運算是怎麼解決的吧。
return n > 0 and n & (n - 1) == 0
若是n是2的冪的話,則其二進制最高位上應該爲1,其他位置都應該爲0,好比8的二進制爲1000;對於n-1的話,則應該是除了最高位爲0,其他都爲1,好比7的二進制爲0111,因此此時n&(n-1)=0。
假如給定一個只包含小寫字母的字符串s1,而後字符串s2是將s1打亂而且向其中插入了一個新的小寫字母,想讓你挑出這個新插入的字母。好比s1 = 'abc',s2 = 'badc',輸出d。
第一種想法就是利用哈希表來統計並存儲每一個元素出現的次數,出現一次的就是新加入的元素,而利用哈希表就意味着須要空間新建字典。而利用按位異或運算只需引入一個第三變量就能夠解決這個問題,利用上文說起的異或性質1和三、以及「相同則假」的法則便可。
m = s1+s2 first = 0 for i in range(0,len(m)): first ^= ord(m[i]) return chr(first)
這裏ord是將字母轉爲對應的Ascii碼,chr則是將Ascii碼數字轉回爲對應的字母形式。
先以8位的二進制舉例,好比輸入的二進制爲000100111,要求輸出爲11001000。
整數翻轉利用字符串就能夠很好的解決,可是二進制不一樣於整數,由於二進制中有不少0,可能在翻轉並轉化以後有些0會被抹去。這裏就能夠利用位運算實現二進制的翻轉,具體代碼以下:
res = 0 for i in range(8): res <<= 1 res += n & 1 n >>= 1 return bin(res)
這裏利用按位與「同真則真,反之則假」的法則,每次將輸入的二進制最後一位與1比較,得出的結果加至res上,而後將n右移一位,由於此時最後一位已經比較過了。
在每次遍歷開始時要注意將res左移一位,由於不論n&1獲得的結果是0仍是1,它在二進制中都是有意義的,須要爲這個即將得出的數提供一個空地,不能夠省略。這裏推薦本身手推一下,很容易就理解了。
仍是給定一個8位的二進制,統計這8個數字中1個數的奇偶性,若1個數爲奇數,則返回1;若1個數爲偶數,則返回0。好比00110111,裏面一共有5個1,因此應該返回1。
這道題利用位運算的思路和上一道有些類似,也是利用按位與「同真則真,反之則假」的法則,循環遍歷這8個數字,用計數器count統計1的個數,最後用count&1判斷count的奇偶性便可,奇數返回1,偶數則返回0。
count = 0 for i in range(8): count += n & 1 n >>= 1 return count & 1
可是這並非位運算最NiuBi之處,下面這種方法纔是真的強,可是可讀性可能不如上面的方法。
n = n^(n>>1) n = n^(n>>2) n = n^(n>>4) return n & 1
仍用00110111舉例,將n與n>>1作異或操做,獲得一個新的二進制,具體以下:
新二進制中0的意義表示該位置與前一個位置共有偶數個1,1則表示該位置與前一個位置共有奇數個1。好比New中第6位的1表示Num1中第5位和第6位共有奇數個1,能夠看到Num1中對應位置爲01是符合的,同理能夠對比一下其餘位置也是具備這個性質。
同理移動2位表示該位置與前三個位置1個數的奇偶性、移動4位表示該位置與前七個位置1個數的奇偶性,因此當移動4位後末位的數字就表示整個8位二進制中1的奇偶性,若是末位爲1,則表明有奇數個1,若是末位爲0,則表明有偶數個1。
位運算可能不少人都知道,可是在解題的時候卻不多用到,由於有不少通俗易懂的方法能夠代替位運算,可是位運算的功能是很是很強大的,值得學習一下。經過上面例子也能夠發現位運算解決某些問題真的是很便捷,但對不理解的人可讀性會比較差,同時這也是位運算能夠ZhuangBi之處。
關注公衆號【奶糖貓】第一時間獲取更多精彩好文