Karatsuba大整数乘法
Karatsuba大整数乘法
题目描述
给定两个任意精度的整数 x、y,使用 Karatsuba 分治算法求其精确乘积。允许正数、负数和0,非整数输入抛出 TypeError。
要求将较大整数拆成高位、低位,递归时用三次较小整数乘法重建乘积。为保证边界清晰,本实现约定两个非负操作数均不超过8个二进制位时,直接使用 Python 的整数乘法;更大的乘法必须按 Karatsuba 公式分解。
示例: 1234 × 5678 返回7006652,-1234 × 5678 返回-7006652,任意整数乘0返回0。
快速幂通过复用相同半次幂减少子调用;本题通过代数恒等式,把原本需要的四个乘法子问题变为三个。这里的规模是整数的二进制位数,不是整数数值大小。
题目分析
从四次乘法到三次乘法
对非负整数,取 ,写成:
直接展开得到:
若分别求 ,需要四次子乘法。Karatsuba 改为只求:
因为 ,所以:
多出的加减和移位均能按位数线性完成,而较大乘法的递归数量减少了一个。
分割、边界与符号
内部函数只处理非负数。令 为两数二进制位数的最大值,取 。右移 位取得高位,与掩码 按位与取得低位;乘以 、 则用左移实现。
当某个操作数为0时立即返回0;当 时直接乘法作为常数规模边界。更大的调用中,高低部分的位数至多约一半;相加可能多一位,但 时子问题仍严格变小,递归必定终止。
外层记录结果符号,对绝对值进行递归,最后恢复符号。两数同号得到非负结果,异号得到非正结果。
正确性依据
边界乘法直接正确。假设三次子乘法均正确,代入上述恒等式即可得到当前两数的精确乘积;位移只是乘以2的幂,并未舍入。最后恢复符号,也符合整数乘法的符号规则。
可结合 MIT 的 Karatsuba 课程资料学习这种减少子乘法的思路。
实现代码
Python 二进制分割实现
def karatsuba(x, y):
if type(x) is not int or type(y) is not int:
raise TypeError("操作数必须是整数")
negative = (x < 0) != (y < 0)
def multiply(a, b):
if a == 0 or b == 0:
return 0
width = max(a.bit_length(), b.bit_length())
if width <= 8:
return a * b
half = width // 2
mask = (1 << half) - 1
a_high, a_low = a >> half, a & mask
b_high, b_low = b >> half, b & mask
z2 = multiply(a_high, b_high)
z0 = multiply(a_low, b_low)
z1 = multiply(a_high + a_low, b_high + b_low)
middle = z1 - z2 - z0
return (z2 << (2 * half)) + (middle << half) + z0
result = multiply(abs(x), abs(y))
return -result if negative else result
for x, y in [(1234, 5678), (-1234, 5678), (-12, -34), (0, 5678), (1, 999)]:
print(f"{x} × {y} = {karatsuba(x, y)}")
输出:
1234 × 5678 = 7006652
-1234 × 5678 = -7006652
-12 × -34 = 408
0 × 5678 = 0
1 × 999 = 999
代码没有用十进制字符串确定位数,而是用 bit_length();低位提取和重组也全部采用二进制操作。阈值8是为了让课堂例子能展示递归,实际工程实现通常会根据运行环境选择更大的阈值。
算法执行过程
主示例中1234有11个二进制位,5678有13个位。因此 width=13、half=6、:
| 子计算 | 调用输入 | 返回结果 |
|---|---|---|
| 高位乘法 | (19, 88) | 1672 |
| 低位乘法 | (18, 46) | 828 |
| 和的乘法 | (37, 134) | 4958 |
| 中间项 | 2458 | |
| 重建乘积 | 7006652 |
三个子调用的最大操作数都不超过8位,直接触发边界乘法。根调用只做三次子乘法,没有单独计算 和 。
输入为 (-1234,5678) 时,内部过程完全相同,最终给7006652加上负号。规模更大的整数则会在三条分支继续采用同样的分割规则。
复杂度分析
设两输入绝对值的最大二进制位数为 ,令 。采用按位运算成本模型:长度为 的整数加减、移位、掩码操作按 计,固定8位以内的乘法按常数计。
时间复杂度: 最坏为 。每次执行三个约半长的子乘法,当前层额外工作为线性;高低位之和可能产生进位,因此更准确的规模上界为:
这个固定的一位增量不改变渐近指数。常见的 写法是忽略进位后的简化模型。两数位数很不均衡或含0时,实际工作可以更少。传统按位竖式乘法的最坏时间为 。
辅助空间复杂度: 位的同时存活的中间整数,递归栈包含 个调用帧;沿一条调用链的操作数长度按几何级数递减。结果本身需要 位。若只数整数对象而不数其位数,会低估大整数存储。
Python 内置大整数乘法自身已有优化,加上本实现的递归开销,不能仅凭渐近式断言这段教学代码会比内置 x * y 更快。
易错点
- 仍然递归计算四个交叉乘积。 会回到四分支结构,失去 Karatsuba 减少子乘法的关键。
- 把 当作中间系数。 它包含 和 ,必须再减去 。
- 高位只左移一次。 对应 ,应左移
2 * half位。 - 直接对负数拆位。 负数的移位和掩码含符号语义,本实现先取绝对值再处理。
- 用数值大小作为递推规模。 分割的是表示长度,应使用位数 分析。
- 把大整数加法视为常数,同时又讨论按位乘法。 分析必须使用一致的计数模型。
课堂练习
- 取十进制 ,将12与34分割,用三次乘法得到结果。
- 主示例的中间系数2458,是否等于 ?
- 同样减半,四次子乘法对应的简化递推是什么?与三次相比结论如何?
- 输入
(-7,0)、(-7,-9)及(7.0,9),分别有什么结果?
参考答案
- 。,中间系数为10,结果为 。这是手算公式;代码采用二进制且此例直接触发8位边界。
- 是。,,合计2458。
- ,得到 ;改为三个子乘法得到 。
- 分别返回0、63,第三个抛出
TypeError,因为7.0为浮点数。
