数值计算#
我们知道, 计算机中的数据是离散的(所有数据本质上都是二进制整数). 对于有理数, 我们可以使用两个整数的比值来精确表示, 但无理数呢? 我们只能进行近似, 如何用离散的计算机来处理(精确近似, 高效计算)无理数这样连续的数据, 是数值计算的核心课题.
高精度平方根#
牛顿切线法(或牛顿法)是用于求方程f(x)=0的数值解的方法, 可以用来做很多事.
比如, 要计算2, 只需求出方程x2−2=0的根即可.
牛顿法的步骤
- 猜测一个根x0.
- 判断这个根是否就是答案(满足精度要求).
- 若不满足, 在x0处建立f(x)的切线. 求出切线与x轴的交点x1
- 将x1作为新的猜测值, 重复步骤2.
公式为
xi+1=xi−f′(xi)f(xi)用牛顿法求b的平方根:
xi+1=xi−f′(xi)f(xi)=2xi+2xib的收敛速度极快. 每次迭代正确的有效数字位数大约会翻一倍, 这种收敛速度被称为二次收敛(Quadratic convergence), 分析如下:
牛顿切线法求平方根的误差收敛速度分析
- 我们假设真实值为x, 那么b=x2, 第i次迭代的猜测xi的相对误差为ϵ. 即xi=x(1+ϵ)
- 第i+1次迭代的相对误差为
xxi+1−1=2xx+xϵ+2x2+2x2ϵb−1=2x(x+xϵ)x2+2ϵx2+ϵ2x2+b−2x2−2ϵx2=2(1+ϵ)ϵ2这与ϵ2是同一个量级.
我们发现, 牛顿切线法求高精度的平方根的过程, 涉及高精度乘法运算和高精度除法运算.
高精度乘除法#
默认读者已经掌握基本的高精度乘法, 比如竖式法.
竖式法的时间复杂度: O(n2)
简单地对高精度乘进行分治并不能改变这个时间复杂度, 依然是O(n2).
高精度乘法: Karatsuba算法#
分治高精度乘(Karatsuba算法)
- 将r进制下n位(n为偶数)的数x分成两个2n位的数, 高位记作x1, 低位记作x0. x=x1rn/2+x0. 同理y=y1rn/2+y0
- 相乘x与y, 展开, 得到xy=x1rn/2y1rn/2+x0y1rn/2+y0x1rn/2+x0y0=x1y1rn+(x0y1+y0x1)rn/2+x0y0.
- 常规分治需要做4次乘法x1y1,x0y1,y0x1,x0y0, 这样做的复杂度是T(n)=3T(2n)+O(n), 这导致总的时间复杂度依然是O(n2).
- Karatsuba注意到, 我们计算x0y1+y0x1其实不需要2次乘法!
- 先计算x1y1,x0y0, 分别记为z1和z2(第一次乘法和第二次乘法), 再计算z3=(x0+x1)(y0+y1)(第三次乘法)
- 我们将z3展开, z3=x0y0+x0y1+x1y0+x1y1, 移项后就有x0y1+x1y0=z3−z1−z2
- 于是我们只用了3次乘法和一些额外的加减法就得到了原来的结果.
- 复杂度分析: T(n)=3T(2n)+O(n), 最后得到T(n)=O(nlog23)≈O(n1.585)
Karatsuba算法目前被用于python大数乘法的部分实现. 这是一个革命性的突破. 它第一次向世界展示: 我们长期以来认为最优的算法, 可能并非最优. 在Karatsuba算法提出以后, 才出现了更多对高精度乘法时间优化的算法, 包括:
- Toom-Cook算法, 将复杂度优化到了O(nlog25)≈O(n1.465)
- Schönhage–Strassen(SSA)算法: 基于FFT算法. 复杂度为 (O(nlognloglogn))
- Fürer 算法, 理论上具有Θ(nlogn2O(log∗n)), 其中log∗n表示渐进logn, 但常数因子巨大, 仅有理论意义. 这个复杂度非常接近O(nlogn). 我们可以猜测, 高精度乘法算法应该有一个O(nlogn)的理论下界.
高精度除法#
要计算高精度的ba, 只需先计算高精度的b1, 再将a与b1进行高精度相乘即可.
所以问题转化为怎么求出高精度倒数b1. 所谓高精度倒数, 其实就是bR的取整, 其中R是一个大数, 并且适合作为除数(通常取10或2的整数次幂), 影响我们的计算精度, R越大, 计算精度越高, 比如R=109代表b的精度达到了10−8.
我们可以使用牛顿切线法. 设x=bR, 则有f(x)=x1−Rb=0(为什么不用bx−R=0呢, 因为这样不方便计算). f′(x)=−x21.
所以每次迭代为xi+1=xi−f′(xi)f(xi)=2xi−Rbxi2. 这里只涉及高精度乘法, 不涉及高精度除法. 于是我们就求出了高精度的b1
高精度除法的误差收敛分析
- xi+1=2xi−Rbxi2, 假设xi=bR(1+ϵ)
- 迭代后误差为xixi+1−1=−ϵ2
故使用牛顿切线法迭代高精度除法也是二次收敛的.
高精度除法的时间复杂度分析
- 假设单次d位的高精度乘法复杂度为O(M(d)).
- 我们知道牛顿切线法需要O(logd)才能达到d位的精度, 所以高精度除法需要迭代d次, 每次迭代需要一次高精度乘法, 所以最初我们认为高精度除法达到d位的时间复杂度为O(M(d)logd)
- 然而, 并不是每一次迭代都需要全精度的高精度乘法. 假如第一次迭代我们需要2位精度, 我们只需要进行2位精度的高精度乘法. 第二次迭代只需要4位精度的高精度乘法…
- 通过等比数列求和, 我们知道高精度除法所需要的时间复杂度为O(D(d))=O(M(d)), 即高精度除法所需的时间复杂度与高精度乘法同阶.
最后, 我们来分析高精度平方根的时间复杂度.
高精度平方根的时间复杂度分析
- 假设单次d位的高精度乘除法复杂度为O(D(d))=O(M(d)). 所以单次迭代的乘除法开销为O(M(d)).
- 假如第一次迭代我们需要2位精度, 我们只需要进行2位精度的高精度乘除法. 第二次迭代只需要4位精度的高精度乘除法…
- 通过等比数列求和, 我们知道高精度平方根所需要的时间复杂度为O(S(d))=O(D(d))=O(M(d)), 即高精度平方根所需的时间复杂度与高精度乘除法同阶.
所以说高精度乘法, 高精度除法, 高精度平方根的时间复杂度同阶. 高精度乘法越快, 高精度除法和平方根的速度也就越快, 这也就是为什么我们需要不断优化高精度乘法的复杂度.
PS: 卡特兰数#
讲义中出现的一种数列, 与本章主要内容无关.
会出现在各种奇怪的问题里:
- 给定n对括号, 问有多少种合法的(正确匹配的)括号序列组合?
- n = 1, ans = 1,
- n = 2, ans = 2,
- n = 3, ans = 5,
- n = 4, ans = 14
- …
- 在一个n×n并且只有一半的三角形方格网(包括对角线)上, 每次只能向上或向右走一步, 从左下角 (0,0) 走到右上角 (n,n)的方案数.
- n = 1, ans = 1,
- n = 2, ans = 2,
- n = 3, ans = 5,
- n = 4, ans = 14
- …
- n个节点的二叉树形态数
- n+1个叶子的满二叉树形态数
- 凸n+2边形的三角剖分数
- n个数的出栈序列数
- …
这些问题都具有某种非负的排列限制, 答案都满足1, 2, 5, 14, …这样的规律, 这个数列就是卡特兰数(Catalan Numbers).
通项公式#
Cn=n+11(n2n)=(n+1)!n!(2n)!
标准递推式#
C0=1,Cn+1=n+22(2n+1)Cn
一种计算更优的递推式#
笔者在做luogu-P1044 时发现的, 相邻两项比值的规律.
C0=1,Cn=n+14n−2Cn−1