最近在看 Algorithms 的分治法(Divide and Conquer)部分,发现书中举的几个例子很有意思,今天想分享的就是第一个例子:用分治法优化整数乘法。这个例子虽然简单,但是非常巧妙。

假设$x$和$y$是两个$n$-bit的整数,为了方便,假设$n$是2的幂次。于是每个数都可以等分成左右各$n/2$bit:

dc_multiplication_1.png

所以$x$和$y$就可以重新写成:

它们的乘积也可以用$x_L$、$x_R$、$y_L$和$y_R$表示:

这样写的好处是可以将$n$-bit整数的乘法转换成4组$n/2$-bit整数的乘法,逐个击破,最后将这四组结果合并。

下面我们来看看“分”和“治”分别花了多少时间。“分”的部分将每个数拆成左右各一半,这可以通过左移(left shift) $n/2$个bit达成,所以耗费$O(n)$的力气;“治”的部分也可以由左移操作完成,耗费$O(n)$的力气。所以,递归式为

等等,解这个递归式会得到$T(n)=O(n^2)$,这和1位1位相乘再相加好像没有区别吧?是的,如果分割成四个子问题的话就和最”naive”的乘法没有区别了,但是这四个子问题之间是有关系的,中间一项$x_Ly_R + x_Ry_L$可以写成

也就是说,我们只要算$x_Ry_R$、$x_Ly_L$和$(x_L + x_R) (y_L + y_R)$这三组乘法就足够了,这就是精妙之处!

优化后的递归式为

利用Master定理可以知道$T(n)=O(n^{\log_2 3})$,大约为$O(n^{1.59})$。

代码

# two 8-bit numbers
x = 0b10011011  # 155
y = 0b10111010  # 186
def multiply(x, y, n):
    """
    Multiply two n-bit positive integers.
    
    Args:
        x: int
        y: int
        n: int, assumed to be power of 2
    Returns:
        prod: int
    """
    if n == 1:
        return x * y
    
    x_l, x_r = divmod(x, 2 ** (n / 2))
    y_l, y_r = divmod(y, 2 ** (n / 2))
    
    p1 = multiply(x_l, y_l, n / 2)
    p2 = multiply(x_r, y_r, n / 2)
    p3 = multiply(x_l + x_r, y_l + y_r, n / 2)
    
    prod = p1 * 2 ** n + (p3 - p1 - p2) * 2 ** (n / 2) + p2
    return prod

multiply(x, y, 8)

输出结果:

28830.0

可以更快吗?

还有没有比这更快的整数乘法算法?答案是有的,需要用到书中之后介绍的快速傅立叶变换(FFT),这也是接下来要填坑的主题。

参考资料

Dasgupta, S., Papadimitriou, C., & Vazirani, U. (2011). Algorithms. Boston: McGraw-Hill Higher Education.