整數的故事(4)——Karastuba算法


  我們在小學就學過用豎式計算兩個多位數的乘法:

  這個過程簡單而繁瑣,沒有最強大腦的普通大眾通常是用計算器代替的。然而對於超大整數的乘法,計算器也未必靠得住,它還存在“溢出”一說。這就需要我們自行編寫算法了。

豎式算法

  雖然對於Python來說,不必太過關心整數的長度和溢出問題,但對於其它編程語言就未必了。這里我們暫且拋開語言本身的特性,只關注算法本身。假設輸入的兩個長整數x和y,它們的乘積將會溢出,所以需要將乘積轉換成字符串,根據乘法豎式的運算規則,很容易寫出下面的代碼:

 1 # 豎式法計算兩個整數相乘
 2 def multi(x, y):
 3     x_str = str(x)
 4     y_str = str(y)
 5     x_len = len(x_str)
 6     y_len = len(y_str)
 7     z = [0] * (x_len + y_len)
 8
 9     for i in range(x_len):
10         for j in range(y_len):
11             z[x_len - i - 1 + y_len - j - 1] += int(x_str[i]) * int(y_str[j])
12
13     for i in range(len(z) - 1):
14         if z[i] >= 10:
15             z[i + 1] += (int(z[i]) // 10)
16             z[i] = int(z[i]) % 10
17
18     return list2str(z)
19
20 # 將z轉換成字符串並刪除左側的0
21 def list2str(z):
22     result = [str(i) for i in z]
23     return ''.join(result[::-1]).lstrip('0')
24
25 # 打印運行結果
26 def paint(a, b):
27     print('{0} * {1} = {2}'.format(a, b, multi(a, b)))
28
29 if __name__ == '__main__':
30     paint(123,321)
31     paint(123,456)
32     paint(123456789000, 987654321000)

  代碼中9~11行用兩個循環模擬了乘法計算的過程,以123×321為例,在循環結束后z將存儲下面的數據:

  11行的for循環是處理進位問題。最后將列表轉換為字符串,再去掉多余的0,打印結果:

Karastuba算法

  豎式乘法偏向於使用蠻力,Karastuba博士在1960年提出了一個更簡單的算法,其思想是把兩個大整數的乘法轉化為若干次小規模的乘法和少量的加法,這就是Karastuba算法。

  對於兩個n位的大整數x和y,可以把x和y分解成兩部分:

  例如:

  是不是有點似成相識?沒錯,這實際上是利用了歐幾里德算式將一個整數分解成m=qn+r的形式。現在x和y的乘積可以表示為:

  這就把原來的大整數乘法變成了四次效較小規模的乘法(其中10n的運算可以通過位移高效處理)和少量加法。上式還可以更進一步:

  看起來更復雜了,但是對於計算機來說,x1y1和x0y0已經計算過了,不需要再次計算。x1y0+x0y1被轉換成一次乘法和少量的加法,多一個加法運算對時間復雜度沒有影響,而減少一個乘法卻能減少時間復雜度。對每一個乘法都進行類似的分解,反復迭代xiyi,直到其中一個乘數只有1位為止。按照這種思路可以編寫新的乘法運算代碼:

 1 # karastuba算法計算兩個n位的大整數乘法, x >=0, y >= 0
 2 def karastuba(x, y, n):
 3     if x == 0 or y == 0:
 4         return 0
 5     elif n == 1:
 6         return x * y
 7
 8     k = n // 2
 9     x1 = x // (10 ** k)
10     x0 = x % (10 ** k)
11     y1 = y // (10 ** k)
12     y0 = y % (10 ** k)
13     z0 = karastuba(x0, y0, k)         # 計算x0y0
14     z1 = karastuba(x1, y1, k)         # 計算x1y1
15     z2 = karastuba((x1 + x0), (y0 + y1), k) - z1 - z0
16
17     return z1 * (10 ** n) + z2 * (10 ** k) + z0

  然而運行時會發現這段代碼很難生效,原因是計算時要求的環境太過理想——每次迭代時xi和yi的位數都必須相同。這就需要重新審視Karastuba算法,看看非理想狀態下是如何計算的。

  假設x和y分別是m位和n位的大整數,x和y可以這樣分解:

  反復迭代xiyi,直到其中一個乘數只有1位為止。

  示例: 123×321 = ?

  

 

  現在可以編寫能夠正確運行的大整數乘法代碼:

 1 def karastuba(x, y):
 2     ''' karastuba算法計算兩個n位的大整數乘法, x >=0, y >= 0 '''
 3     if x == 0 or y == 0:
 4         return 0
 5     m, n = len(str(x)), len(str(y)) # x和y的位數
 6     if m == 1 or n == 1: # 如果x或y只有1位,直接計算結果
 7         return x * y
 8     m //= 2
 9     x1, x0 = x // (10 ** m), x % (10 ** m) # 分解x
10     n //= 2
11     y1, y0 = y // (10 ** n), y % (10 ** n) # 分解y
12     # 迭代分解夠的4個較小規模的乘法
13     x1y1 = karastuba(x1, y1)
14     x1y0 = karastuba(x1, y0)
15     x0y1 = karastuba(x0, y1)
16     x0y0 = karastuba(x0, y0)
17     return x1y1 * (10 ** (m + n)) + x1y0 * (10 ** m) + x0y1 * (10 ** n) + x0y0
18 
19 def paint(x, y):
20     ''' 在控制台打印karastuba(x, y)的運行結果 '''
21     print('{0} * {1} = {2}, ({3})'.format(x, y, karastuba(x, y), x * y))
22 
23 if __name__ == '__main__':
24     paint(123, 321)
25     paint(123456789, 987456)
26     paint(1234567891234567, 1234567891234567)

 

  先看看windows計算器下1234567891234567×1234567891234567的運行結果:

  計算器已經無法給出精確的結果,但karastuba沒有問題:

  對於非10進制整數,Karastuba算法依然適用。

  


   作者:我是8位的

  出處:http://www.cnblogs.com/bigmonkey

  本文以學習、研究和分享為主,如需轉載,請聯系本人,標明作者和出處,非商業用途! 

  掃描二維碼關注公眾號“我是8位的”


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM