计数 · 取幂 · 大整数乘法
分治最出名的实例是排序,但排序恰好是最不需要动脑的那一类:切成两半、各自递归、线性合并, 摆在明面上。本页的三个例子各自动了别的地方:一个在合并时多算了一样东西,一个折半的对象根本不是问题规模,一个把 从 4 压到了 3。
1 · 归并顺带数出的 inversion
序列 里满足 且 的下标对叫一个 inversion。它计量的是「离排好序还有多远」:升序序列有 0 个,倒序序列有 个。二重循环数一遍是 。
按下标落在哪一半,inversion 分成三类:两个下标都在左半、都在右半、一左一右。前两类就是两个规模减半的同类子问题,第三类必须在合并处算掉。
合并时两半已各自有序。从右半取走 的那一刻,左半还没被取走的元素全都比 大,而它们的原始下标又全在 之前,于是这一步一次记下 个 inversion。左半剩几个是现成的读数,不必再比较。整个算法的形状与 merge sort 完全一样,,代价 。
注 · 递归回来时两半已被排过序,元素的位置全乱了,跨半区的 inversion 数却不受影响:左半内部怎么重排,都不改变左半任一元素与右半任一元素的大小关系,而跨半区 inversion 只由这种关系决定。左右两半各自内部的 inversion 已经在递归里数过,排序把它们清零并不丢账。
警示 · 比较写成
走左半,不能写成
。相等的两个元素不构成 inversion,
会在遇到相等时改走右半,把左半剩余长度错记一笔;同一处改动还会让归并失去稳定性。classics.test.ts 里 [2, 2, 2, 2] 应得 0 这条断言守的就是它。
2 · 折半的是指数
连乘要 次乘法。折半的对象换成指数:,奇数时多乘一个 。递归式是
对照 分治的骨架与复杂度 §3.2 的表,、、:分水岭 与 同阶,属情形二,答案 。每层只剩一个子问题, 因子全部来自层数。
自底向上的写法把递归摊平:按指数的二进制位从低到高走,维护一个不断自乘的 base,遇到为 1 的位就把它乘进累加器。乘法次数等于二进制位里 1 的个数加上平方次数,也就是
加上 popcount。powFrames 在
上用掉 59 次乘法,朴素连乘要 1073741822 次。
这套折半只依赖乘法的结合律,与被乘的东西是什么无关。换成 矩阵,就得到线性递推的对数解法:
求 只需 7 次矩阵乘法,而逐项递推要走 40 步。差距在 大时才真正显出来: 的 Fibonacci 取模,线性递推走不完,矩阵快速幂是 60 次矩阵乘法。
警示 · 标量版的模数取的是 1000003,不是竞赛里常见的 1000000007。JS 的 number 是 double,精确整数只到
;两个接近
的数相乘会到
,超出后不报错,只是静默丢掉低位。取
之后乘积不到
,仍在精确区间内。要用大模数就得换 BigInt 或把乘法拆成高低位两段。
3 · Karatsuba 的第三次乘法
两个 位十进制数相乘,竖式要做 次一位数乘法。把两个数各从中间切开,记 :
直接照这个式子算要四次半长乘法,。分水岭是 ,比 大,属情形一,答案仍是 。切了等于白切。
Karatsuba 的观察是中间那一项不必单独乘出来。展开
右边后两项正是已经算出的 与 ,把它们加回去就得到中间项。三次半长乘法加上若干次加减法:
分水岭降到 ,仍属情形一,答案变成 。 一动没动,省下来的全部来自 从 4 变成 3。
karatsuba 实测的一位数乘法次数:4 位 9 次对竖式的 16 次,8 位 27 次对 64 次,16 位 81 次对 256 次。倍率恰好是每加倍一次位宽,乘法次数乘 3 而非乘 4。
注 · 教科书多写成
的加法变体,实现里换成了上面的减法变体,原因是递归树的形状。两个
位数相加会进位到
位,那一支递归下去每层都要多带一位,树就不再齐整,叶子数也对不上
。差值
则始终落在
内,位宽严格减半,代价是要处理负数的符号。classics.test.ts 直接断言了这个结构:8 位输入下叶子恰好 27 个、内部结点 13 个、树高 3。
警示 · 预设里的 8 位被乘数是挑过的。整个实现走的是 double,而 8 位数最大可到 99999999,平方后约 ,已经越过 。预设取 12345678 与 87654321,乘积约 ,仍然精确。要跑真正的大整数得把数字换成数组表示,那时基例的「一位数乘法」才当得起这个名字。
Strassen 的矩阵乘法是同一个套路换了个舞台:把 矩阵切成四个 阶块,朴素做法要八次块乘(,答案 ),Strassen 用七次加上大量加减法()。压的同样是 。
4 · 参考文献
- Karatsuba, A., & Ofman, Y. (1962). Multiplication of many-digital numbers by automatic computers. Doklady Akademii Nauk SSSR, 145(2), 293–294.
- Strassen, V. (1969). Gaussian elimination is not optimal. Numerische Mathematik, 13(4), 354–356.
- Knuth, D. E. (1997). The Art of Computer Programming, Volume 2: Seminumerical Algorithms (3rd ed.). Addison-Wesley.
- Kleinberg, J., & Tardos, É. (2005). Algorithm Design. Addison-Wesley.