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

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

dc_multiplication_1.png

所以xy就可以重新写成:

x=2n/2xL+xRy=2n/2yL+yR.

它们的乘积也可以用xLxRyLyR表示:

xy=2nxLyL+2n/2(xLyR+xRyL)+xRyR.

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

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

T(n)=4T(n/2)+O(n).

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

xLyR+xRyL=(xL+xR)(yL+yR)xLyLxRyR.

也就是说,我们只要算xRyRxLyL(xL+xR)(yL+yR)这三组乘法就足够了,这就是精妙之处!

优化后的递归式为

T(n)=3T(n/2)+O(n),

利用Master定理可以知道T(n)=O(nlog23),大约为O(n1.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.