有关多项式的算法
多项式
一个度数为
如,下式是一个度数为
用
通常有两种表示多项式的方法:系数表达和点值表达。
系数表达即按照从低次项到高次项1的顺序将每个项的系数放入一个向量中。如上面的多项式
点值表达,顾名思义就是给定一些点,计算出多项式在这些点的值。
计算多项式在一个点
这样可以在
计算
因此之前的多项式可以表示成以下的形式:
可以证明,如果选择的求值点互不相同,那么一个含有
因为点值表达可以用下面的矩阵方程表示出来:
方程左边的矩阵是一个范德蒙德矩阵,其行列式的值为:
因此只要
这意味着它一定会有一个逆矩阵,从而可以将方程右边的列向量乘以逆矩阵,就可以得到原多项式的系数向量。
多项式加法
多项式加法是很简单的,只需要将对应次数的项的系数相加即可。
如果是点值表达,则需要两个多项式的采样点是一样的,然后对应点的值才可以直接相加减,最后得到的是新的多项式的点值表达。
两个多项式
多项式乘法
如果采用系数表达,我们可以暴力地将多项式乘开然后合并同类项,这样可以在
相反,如果采用点值表达,则只需要
然而,点值表达方式并没有系数表达更有用。考虑能否利用点值表达的多项式乘法线性时间来加快系数表达的多项式乘法。
一个直接的方法就是先对原多项式在
然而并没有什么很快的采样/插值算法,但是使用快速傅立叶变换可以在
复数
快速傅立叶变换是用于快速计算离散傅立叶变换 (DFT)的结果的算法。由于 DFT 涉及到复数的运算,因此这里先扯一点复数的基本知识。
首先定义单位复数根
单位复数根与自然对数的底数
为了证明这个等式,首先考虑一下
我们注意到
和式左边就是
因此:
为了方便之后的公式书写,这里定义
从几何的角度来讲,相当于将一个周角均分成
DFT
接下来介绍 DFT。标准的 DFT 是作用在一个长度为
我们当然可以从
如何可以得到这个逆 DFT 公式呢?可以将 DFT 的过程视为一次矩阵乘法:
设等式左边的矩阵为
因为
由于几何级数的求和对复数也适用,所以:
注意,只有在
于是
FFT
如果根据 DFT 的定义来计算,需要
下面为了讨论方便,假设所有的给 FFT 处理的序列长度都是
快速傅立叶变换是基于分治思想的。算法首先将序列分为两部分,一部分的元素的下标为偶数,另一部分为奇数:
可以注意到,下标为偶数就是下标的二进制表示的最后一位为
分成两部分后,对于每一部分递归求解。然后尝试将这两部分合并,得到原序列的 DFT。
考虑假设我们获得了变换后的
因此:
将单位复数根带入:
由此我们获得了如何将两部分合并为一部分的方法。
对于逆变换,可以使用同样的方法,只是单位复数根的次数的符号恰好相反,并且最后需要对每一个数除以
FFT 实现
递归式 FFT
递归实现 FFT 的过程非常简单,首先判断序列长度是否为
否则按照下标的奇偶性分为两部分,然后递归求解。
最后合并这两部分的结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | function RECURSIVE-FFT(x, reverse = false): if x.length == 1: # 如果长度为1 return x 将序列分为a[0]和a[1] a'[0] = RECURSIVE-FFT(a[0]) a'[1] = RECURSIVE-FFT(a[1]) X = [0..x.length - 1] if reverse: # 如果是逆变换 w_n = exp(0 + (-2 * pi / n)i) else: w_n = exp(0 + (2 * pi / n)i) w = 1 for k in [0, x.length / 2 - 1]: t = w * a'[1][k] X[k] = a'[0] + t X[x.length / 2 + k] = a'[0] - t w *= w_n return X |
迭代式 FFT
与递归式 FFT 相比,效率更高的是无需递归的迭代式。
首先考虑求值的顺序。对于一个下标而言,它的二进制表示实际上已经决定了它将要移动的路径。
即从低位开始,如果是
更进一步,观察划分完之后的序列,把每一个下标所对应的二进制翻转过来,这恰好是递增的顺序。
因此我们可以实现一个从高位到低位的加法器,来计算下一个元素该是谁。利用位运算不难实现这个加法器。
这个算法被称作雷德算法 (Rader’s algorithm)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | function RADER(x): n = x.length X = [0..n - 1] k = 0 for i in [0..n - 1]: X[i] = x[k] y = n >> 1 # n 是 2 的幂次 while k & y: k ^= y y >>= 1 k |= y return X |
这样就可以按照顺序进行合并,第一次每
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | function ITERATIVE-FFT(x, reverse = false): x = RADER(x) n = x.length s = 2 while s <= n: if reverse: # 如果是逆变换 w_n = exp(0 + (-2 * pi / n)i) else: w_n = exp(0 + (2 * pi / n)i) for i in [0, n] by s: left = i right = i + s - 1 mid = (left + right) / 2 w = 1 for k in [0, mid - 1]: t = w * x[mid + k] x[mid + k] = x[left + k] - t x[left + k] = x[left + k] + t w *= w_n s *= 2 return x |
多项式乘法
前面讲了那么多 DFT,那多项式乘法与 DFT 有什么关系呢?
事实上,DFT 实际上就是一次在多项式上采样的过程。
获得了点值表达之后,就可以在
最后进行一遍逆 DFT 可以得到结果!
时间复杂是
需要注意的是,上面的 FFT 需要序列长度为
实际实现中,需要注意