2029 字
10 分钟
算法导论(4) 数值计算杂项
2025-09-17
无标签

数值计算#

我们知道, 计算机中的数据是离散的(所有数据本质上都是二进制整数). 对于有理数, 我们可以使用两个整数的比值来精确表示, 但无理数呢? 我们只能进行近似, 如何用离散的计算机来处理(精确近似, 高效计算)无理数这样连续的数据, 是数值计算的核心课题.

高精度平方根#

牛顿切线法(或牛顿法)是用于求方程f(x)=0f(x) = 0的数值解的方法, 可以用来做很多事.

比如, 要计算2\sqrt{2}, 只需求出方程x22=0x^2 - 2 = 0的根即可.

牛顿法的步骤
  1. 猜测一个根x0x_0.
  2. 判断这个根是否就是答案(满足精度要求).
  3. 若不满足, 在x0x_0处建立f(x)f(x)的切线. 求出切线与xx轴的交点x1x_{1}
  4. x1x_{1}作为新的猜测值, 重复步骤2.

公式为

xi+1=xif(xi)f(xi)x_{i+1} = x_i - \frac{f(x_i)}{f'(x_i)}

用牛顿法求b的平方根:

xi+1=xif(xi)f(xi)=xi2+b2xix_{i+1} = x_i - \frac{f(x_i)}{f'(x_i)} = \frac{x_i}{2} + \frac{b}{2x_i}

的收敛速度极快. 每次迭代正确的有效数字位数大约会翻一倍, 这种收敛速度被称为二次收敛(Quadratic convergence), 分析如下:

牛顿切线法求平方根的误差收敛速度分析
  1. 我们假设真实值为xx, 那么b=x2b = x^2, 第ii次迭代的猜测xix_i的相对误差为ϵ\epsilon. 即xi=x(1+ϵ)x_i = x(1 + \epsilon)
  2. i+1i + 1次迭代的相对误差为
xi+1x1=x+xϵ2x+b2x2+2x2ϵ1=x2+2ϵx2+ϵ2x2+b2x22ϵx22x(x+xϵ)=ϵ22(1+ϵ)\frac{x_{i+1}}{x} - 1 = \frac{x+x\epsilon}{2x} + \frac{b}{2x^2 + 2x^2\epsilon} - 1 = \frac{x^2 + 2\epsilon x^2 + \epsilon^2 x^2 + b - 2x^2 - 2\epsilon x^2}{2x(x+x\epsilon)} = \frac{\epsilon^2}{2(1 + \epsilon)}

这与ϵ2\epsilon^2是同一个量级.

我们发现, 牛顿切线法求高精度的平方根的过程, 涉及高精度乘法运算和高精度除法运算.

高精度乘除法#

默认读者已经掌握基本的高精度乘法, 比如竖式法.

竖式法的时间复杂度: O(n2)O(n^2)

简单地对高精度乘进行分治并不能改变这个时间复杂度, 依然是O(n2)O(n^2).

高精度乘法: Karatsuba算法#

分治高精度乘(Karatsuba算法)
  1. rr进制下nn位(n为偶数)的数xx分成两个n2\frac{n}{2}位的数, 高位记作x1x_1, 低位记作x0x_0. x=x1rn/2+x0x = x_1r^{n/2} + x_0. 同理y=y1rn/2+y0y = y_1r^{n/2} + y_0
  2. 相乘xxyy, 展开, 得到xy=x1rn/2y1rn/2+x0y1rn/2+y0x1rn/2+x0y0=x1y1rn+(x0y1+y0x1)rn/2+x0y0xy = x_1r^{n/2}y_1r^{n/2} + x_0y_1r^{n/2} + y_0x_1r^{n/2} + x_0y_0 = x_1y_1r^n + (x_0y_1 + y_0x_1)r^{n/2} + x_0y_0.
  3. 常规分治需要做4次乘法x1y1,x0y1,y0x1,x0y0x_1y_1, x_0y_1, y_0x_1, x_0y_0, 这样做的复杂度是T(n)=3T(n2)+O(n)T(n) = 3T(\frac{n}{2}) + O(n), 这导致总的时间复杂度依然是O(n2)O(n^2).
  4. Karatsuba注意到, 我们计算x0y1+y0x1x_0y_1 + y_0x_1其实不需要2次乘法!
  5. 先计算x1y1,x0y0x_1y_1, x_0y_0, 分别记为z1z_1z2z_2(第一次乘法和第二次乘法), 再计算z3=(x0+x1)(y0+y1)z_3 = (x_0 + x_1)(y_0 + y_1)(第三次乘法)
  6. 我们将z3z_3展开, z3=x0y0+x0y1+x1y0+x1y1z_3 = x_0y_0 + x_0y_1 + x_1y_0 + x_1y_1, 移项后就有x0y1+x1y0=z3z1z2x_0y_1 + x_1y_0 = z_3 - z_1 - z_2
  7. 于是我们只用了3次乘法和一些额外的加减法就得到了原来的结果.
  8. 复杂度分析: T(n)=3T(n2)+O(n)T(n) = 3T(\frac{n}{2}) + O(n), 最后得到T(n)=O(nlog23)O(n1.585)T(n) = O(n^{\log_2{3}}) \approx O(n^{1.585})

Karatsuba算法目前被用于python大数乘法的部分实现. 这是一个革命性的突破. 它第一次向世界展示: 我们长期以来认为最优的算法, 可能并非最优. 在Karatsuba算法提出以后, 才出现了更多对高精度乘法时间优化的算法, 包括:

  1. Toom-Cook算法, 将复杂度优化到了O(nlog25)O(n1.465)O(n^{log_2 5}) \approx O(n^{1.465})
  2. Schönhage–Strassen(SSA)算法: 基于FFT算法. 复杂度为 (O(nlognloglogn))(O(n \log n \log \log n))
  3. Fürer 算法, 理论上具有Θ(nlogn2O(logn))\Theta (n\log n2^{O(\log *n)}), 其中logn\log *n表示渐进logn\log n, 但常数因子巨大, 仅有理论意义. 这个复杂度非常接近O(nlogn)O(n\log n). 我们可以猜测, 高精度乘法算法应该有一个O(nlogn)O(n\log n)的理论下界.

高精度除法#

要计算高精度的ab\frac{a}{b}, 只需先计算高精度的1b\frac{1}{b}, 再将aa1b\frac{1}{b}进行高精度相乘即可.

所以问题转化为怎么求出高精度倒数1b\frac{1}{b}. 所谓高精度倒数, 其实就是Rb\frac{R}{b}的取整, 其中RR是一个大数, 并且适合作为除数(通常取10或2的整数次幂), 影响我们的计算精度, RR越大, 计算精度越高, 比如R=109R = 10^9代表b的精度达到了10810^{-8}.

我们可以使用牛顿切线法. 设x=Rbx = \frac{R}{b}, 则有f(x)=1xbR=0f(x) = \frac{1}{x} - \frac{b}{R} = 0(为什么不用bxR=0bx - R = 0呢, 因为这样不方便计算). f(x)=1x2f'(x) = -\frac{1}{x^2}.

所以每次迭代为xi+1=xif(xi)f(xi)=2xibxi2Rx_{i+1} = x_i - \frac{f(x_i)}{f'(x_i)} = 2x_i- \frac{bx_i^2}{R}. 这里只涉及高精度乘法, 不涉及高精度除法. 于是我们就求出了高精度的1b\frac{1}{b}

高精度除法的误差收敛分析
  1. xi+1=2xibxi2Rx_{i + 1} = 2x_i - \frac{bx_i ^2}{R}, 假设xi=Rb(1+ϵ)x_i = \frac{R}{b}(1+\epsilon)
  2. 迭代后误差为xi+1xi1=ϵ2\frac{x_{i + 1}}{x_i} - 1 = -\epsilon ^ 2

故使用牛顿切线法迭代高精度除法也是二次收敛的.

高精度除法的时间复杂度分析
  1. 假设单次dd位的高精度乘法复杂度为O(M(d))O(M(d)).
  2. 我们知道牛顿切线法需要O(logd)O(\log d)才能达到dd位的精度, 所以高精度除法需要迭代dd次, 每次迭代需要一次高精度乘法, 所以最初我们认为高精度除法达到d位的时间复杂度为O(M(d)logd)O(M(d)\log d)
  3. 然而, 并不是每一次迭代都需要全精度的高精度乘法. 假如第一次迭代我们需要2位精度, 我们只需要进行2位精度的高精度乘法. 第二次迭代只需要4位精度的高精度乘法…
  4. 通过等比数列求和, 我们知道高精度除法所需要的时间复杂度为O(D(d))=O(M(d))O(D(d)) = O(M(d)), 即高精度除法所需的时间复杂度与高精度乘法同阶.

最后, 我们来分析高精度平方根的时间复杂度.

高精度平方根的时间复杂度分析
  1. 假设单次dd位的高精度乘除法复杂度为O(D(d))=O(M(d))O(D(d)) = O(M(d)). 所以单次迭代的乘除法开销为O(M(d)).
  2. 假如第一次迭代我们需要2位精度, 我们只需要进行2位精度的高精度乘除法. 第二次迭代只需要4位精度的高精度乘除法…
  3. 通过等比数列求和, 我们知道高精度平方根所需要的时间复杂度为O(S(d))=O(D(d))=O(M(d))O(S(d)) = O(D(d)) = O(M(d)), 即高精度平方根所需的时间复杂度与高精度乘除法同阶.

所以说高精度乘法, 高精度除法, 高精度平方根的时间复杂度同阶. 高精度乘法越快, 高精度除法和平方根的速度也就越快, 这也就是为什么我们需要不断优化高精度乘法的复杂度.

PS: 卡特兰数#

讲义中出现的一种数列, 与本章主要内容无关. 会出现在各种奇怪的问题里:

  1. 给定nn对括号, 问有多少种合法的(正确匹配的)括号序列组合?
    1. n = 1, ans = 1,
    2. n = 2, ans = 2,
    3. n = 3, ans = 5,
    4. n = 4, ans = 14
  2. 在一个n×nn \times n并且只有一半的三角形方格网(包括对角线)上, 每次只能向上或向右走一步, 从左下角 (0,0)(0,0) 走到右上角 (n,n)(n,n)的方案数.catalan
    1. n = 1, ans = 1,
    2. n = 2, ans = 2,
    3. n = 3, ans = 5,
    4. n = 4, ans = 14
  3. n个节点的二叉树形态数
  4. n+1个叶子的满二叉树形态数
  5. 凸n+2边形的三角剖分数
  6. n个数的出栈序列数

这些问题都具有某种非负的排列限制, 答案都满足1, 2, 5, 14, …这样的规律, 这个数列就是卡特兰数(Catalan Numbers).

通项公式#

Cn=1n+1(2nn)=(2n)!(n+1)!n!C_n = \frac{1}{n+1} \binom{2n}{n} = \frac{(2n)!}{(n+1)!n!}

标准递推式#

C0=1,Cn+1=2(2n+1)n+2CnC_0=1, \quad C_{n+1} = \frac{2(2n+1)}{n+2} C_n

一种计算更优的递推式#

笔者在做luogu-P1044 时发现的, 相邻两项比值的规律.

C0=1,Cn=4n2n+1Cn1C_0=1, \quad C_n = \frac{4n - 2}{n + 1}C_{n - 1}
算法导论(4) 数值计算杂项
https://fuwari.vercel.app/posts/算法导论4-数值计算/
作者
ykindred
发布于
2025-09-17
许可协议
CC BY-NC-SA 4.0