你不知道的按位運算

先來看LeetCode上的Divide Two Integers題目要求:html

Divide two integers without using multiplication, division and mod operator.python

就是說不用乘法,除法,求模運算來實現兩個整數相除,看起來很簡單,我能夠用除數減去被除數,直到除數小於被除數,記錄減法操做的次數便可。假設是計算m/n,那麼時間複雜度爲O(m/n)。用Python實現後,Time Limit Exceeded。咱們考慮有沒有更加優化的算法呢?git

若是很難想獲得,那就先來回憶下二進制數按位運算的一些知識。github

二進制數按位運算

計算機裏面全部數據都存儲爲0,1串,全部的運算歸根到底都轉爲二進制數的運算。相信你們都知道二進制數按位運算的規則:面試

二進制位運算規則

來看一些簡單的例子:算法

1010 & 1100 = 1000
1010 | 1100 = 1110
1010 ^ 1100 = 0110
1010 << 2   = 101000
1010 >> 2   = 10
~1010       = 0101

單純的二進制位之間的這些運算至關簡單,但對咱們實際編程並無直接幫助,由於編程過程當中須要的常常是數字間的運算,好比 5*(2^4) 。真的是這樣嗎?接着往下看!編程

計算機中數字的存儲方式

咱們都知道計算機中萬物皆爲0、1,將萬物變爲0、1的過程叫作編碼,這裏咱們只討論將數字編碼爲0、1的過程。windows

計算機中對數字的表示有三種方式:原碼,反碼,補碼:數組

  • 原碼錶示法在數值前面增長了一位符號位(即最高位爲符號位):正數該位爲0,負數該位爲1。好比十進制3若是用8個二進制位來表示就是 00000011, -3就是 10000011。編程語言

  • 反碼錶示方法:正數的反碼是其自己;負數的反碼是在其原碼的基礎上,符號位不變,其他各個位取反。

  • 補碼錶示方法:正數的補碼是其自己;負數的補碼是在其原碼的基礎上,符號位不變,其他各位取反,最後+1。 (即在反碼的基礎上+1)

原碼容易被人腦直接識別並用於計算,可是對於計算機來講並不友好。因此在計算機系統中,數值一概用補碼來表示、運算和存儲。使用補碼,能夠將符號位和數值域統一處理,將加法和減法統一處理。此外,補碼與原碼相互轉換,其運算過程是相同的,不須要額外的硬件電路。詳細的解釋能夠參考原碼, 反碼, 補碼詳解

數字的按位運算

計算機中數字存儲爲補碼形式,各個數之間的運算也是對它們的補碼作運算,並且獲得的結果也是補碼,以下圖:

補碼運算

各類編程語言都提供了對補碼的二進制位直接進行運算的方法。以Python爲例:

>>> 0b1010 & 0b1100
8   #1000
>>> 0b1010 | 0b1100
14  #1110
>>> 0b1010 ^ 0b1100
6   #0110
>>> 0b1010 << 2
40  #101000
>>> 0b1010 >> 2
2   #10
>>> ~0b1010
-11 #10000000 00000000 00000000 00001011
>>> type(0b1010)
<type 'int'>

上面0b開頭的0、1串表示整型數字,在32位操做系統中,Python中int類型通常佔32個二進制位,以最後一個求反運算爲例子,1010的補碼爲

00000000 00000000 00000000 00001010

求反操做後爲:

11111111 11111111 11111111 11110101

即爲-11(原碼爲:10000000 00000000 00000000 00001011)的補碼。(對一個數的補碼求補碼便可獲得該數的原碼)

另闢蹊徑的按位運算

那麼按位運算在實際編程中能夠扮演哪些角色呢?簡單點地,能夠用來判斷奇、偶數:num & 0x1,或者對一個數變換符號:~num + 1;複雜點的能夠用來交換兩個數,求絕對值等等。

> 不用額外的變量實現兩個數字互換。

def swap(num_1, num_2):
    num_1 ^= num_2
    num_2 ^= num_1
    num_1 ^= num_2
    return num_1, num_2

證實很簡單,咱們只須要明白異或運算知足下面規律:

  • 0^a = a;

  • a^a = 0;

  • a^b^c = a^c^b;

巧妙運用異或能夠高效解決不少問題,好比 找出數組中只出現了一次的數(除了一個數只出現一次外,其餘數都是出現兩次),以及它的升級版:數組中只出現1次的兩個數字(百度面試題)

> 不用判斷語句來實現求絕對值。

def bit_abs(num):
    negative = num >> 31
    return (num ^ negative) - negative

這裏假設程序運行環境中操做系統爲32位,int型整數(不考慮整數溢出)用32位存儲,所以能夠用 num>>31 取出符號位,後面的部分留給大夥證實。

Leetcode 題目思路

回到文章開始提到的題目中,咱們對除數減去被除數的過程稍做改進。假設求m/n,咱們不一次次的 m-n,而是找到n的一個倍數,使得m-x*n儘量小,這樣能減小循環減法的次數,進而提升效率。咱們知道在按位操做中,n << k至關於 n * 2^k,所以能夠用2^k 來找合適的x。

咱們須要這樣的一個數字k,它使得n 2^k < m < n 2^(k+1), 而後用m - n*2^k,獲得新的m'。再找相應的k',作減法,如此循環便可。代碼放在這裏

原文地址

相關閱讀
Pyhon wiki: BitwiseOperators
位操做基礎篇之位操做全面總結

相關文章
相關標籤/搜索