引言
在学Python时突发奇想,用Python实现乘法,然后了解到我们一般手写乘法时用的是小学生算法,对于计算机,如果乘数过大,如32*20位的数,无论怎样扩展,固定的整数类型总是有表达的极限。如果对超级大整数进行精确运算,一个简单的办法是:把大整数的运算化解为若干小整数的运算,即所谓:“分块法“,其中一种算法就是Karatsuba算法。我把小学生算法和Karatsuba算法比较发现Karatsuba算法其实就是小学生算法的优化版。
小学生算法
大多数人所学乘法的运算方法都是以下这种方法。将两个乘数排成两行,用下面的乘数中的每一位数字分别去乘以上面的乘数的每一位数字,然后将所有的相乘结果相加。比如说,如果是两个两位数的乘法运算,你需要进行四次两个一位数的相乘,然后将这四个相乘的结果相加。
这个我们在小学就学过的乘法的算法,在进行 n 位数之间的相乘时,需要进行大约 n 的平方次个位数的相乘,这里 n 是每个乘数的位数。所以,两个三位数的乘法需要进行 9 次个位数的相乘,而如果你要进行的是两个 100 位数的大数相乘,就需要 10,000 次个位数相乘。
小学生算法思路
小学生算法也可以看作一种"分块法",不过每块一个数字。
分别将X,Y分为个、十、百、千、万等位次,即X0为X个位上的数字,X1为十位上的数字,以此类推。这样我们只需计算个位数的相乘。但是缺点很明显,如果进行两个n位数的相乘,则需要计算n的2次方次乘法。
Python实现小学生算法的逻辑:
# 小学生算法
def xxssf(x, y):
if x < 10 and y < 10:
return x * y
xx = [int(i) for i in str(x)]
yy = [int(i) for i in str(y)]
w = abs(len(xx) - len(yy))
if w != 0: # 位数不一致则在较小的数的高位补0
if len(xx) > len(yy):
for i in range(w):
yy = [0] + yy
else:
for i in range(w):
xx = [0] + xx
result = 0
m = len(xx) # 因为已经补位len(xx)==len(yy)
for i in range(m):
x_i = xx[i]
for n in range(m):
y_n = yy[n]
result += y_n * x_i * 10 ** ((m - i - 1) + (m - n - 1))
return result
Karatsuba算法
Karatsuba算法也是先分块,不过不再是和小学生算法一样有几位数就分几块了,Karatsuba算法每次分两块。
H代表高位部分,L代表低位部分,b为位次。
可以发现,到这一步还是要进行n的2次方次乘法运算,进行一下变式:
这样计算p1,p2,p3三次乘法后只需加减法,相较未变式之前而言少计算了一次乘法。但是很显然对于位数小的数字相乘,这种算法优势并不明显。
Python实现逻辑:
from math import ceil, floor
# math.ceil(x) 返回大于或等于x的最小整数
# math.floor(x) 返回小于或等于x的最大整数
# Karatsuba算法
def karatsuba(x, y):
# base case
if x < 10 and y < 10: # in other words, if x and y are single digits
return x * y
n = max(len(str(x)), len(str(y)))
m = ceil(n / 2)
x_H = floor(x / 10 ** m)
x_L = x % (10 ** m)
y_H = floor(y / 10 ** m)
y_L = y % (10 ** m)
# recursive steps
a = karatsuba(x_H, y_H)
d = karatsuba(x_L, y_L)
e = karatsuba(x_H + x_L, y_H + y_L) - a - d
return int(a * (10 ** (m * 2)) + e * (10 ** m) + d)
结论
小学生算法yyds