给定两个多项式 A(x) 和 B(x),求乘积 C(x) = A(x)*B(x)。已经有一个 O( ) 天真的方法来解决这个问题。这里。这种方法使用多项式的系数形式来计算乘积。
多项式的系数表示是 a = a0, a1, …, an-1。
• 例子-
A(x) = (9, -10, 7, 6) 的系数表示
B(x) = (-5, 4, 0, -2) 的系数表示
输入:A[] = {9, -10, 7, 6} B[] = {-5, 4, 0, -2} 输出:
如果我们以另一种形式表示多项式,我们可以做得更好。
想法是以点值形式表示多项式,然后计算乘积。度数为 n 的多项式 A(x) 的点值表示是一组 n 个点值对是{ (x0, y0), (x1, y1), …, (xn-1, yn-1 )} 使得所有的 xi 都是不同的,并且 yi = A(xi) 对于 i = 0, 1, …, n-1。
例子
xi — 0, 1, 2, 3 A(xi) — 1, 0, 5, 22
上述多项式的点值表示为 { (0, 1), (1, 0), (2, 5), (3, 22) }。使用霍纳的方法,(这里讨论),n 点评估需要时间 O( )。它只是计算 n 个不同点的某个 x 处的 A(x) 值,因此时间复杂度为 O( )。现在多项式转换为点值,可以很容易地再次使用 horner 方法计算 C(x) = A(x)*B(x)。这需要 O(n) 时间。这里重要的一点是 C(x) 的度界为 2n,那么 n 个点将只给出 C(x) 的 n 个点,因此对于这种情况,我们需要 2n 个不同的 x 值来计算 2n 个不同的 y 值。现在计算出乘积,可以将答案转换回系数向量形式。为了回到系数向量形式,我们使用这个评估的逆。求值的逆过程称为插值。使用拉格朗日公式的插值将点值形式转化为多项式的系数向量形式。拉格朗日公式是 –
到目前为止,我们讨论过,
.
这个想法还是解决了O( ) 时间复杂度。我们可以使用任何我们想要的点作为评估点,但是通过仔细选择评估点,我们可以在 O(n log n) 时间内在表示之间进行转换。如果我们选择“复单位根”作为评估点,我们可以通过对系数向量进行离散傅立叶变换(DFT)来产生点值表示。我们可以执行逆运算,即插值,通过对点值对进行“逆 DFT”,产生一个系数向量。快速傅立叶变换 (FFT) 可以在 O(nlogn) 时间内执行 DFT 和逆 DFT。
离散傅立叶变换
DFT 正在评估 n 个复数 n 次单位根处的多项式的值 .因此对于 k = 0, 1, 2, …, n-1, y = (y0, y1, y2, …, yn-1) 是给定多项式的离散傅立叶变换 (DFT)。
度数为 n 的两个多项式的乘积是度数为 2n 的多项式。因此,在计算输入多项式 A 和 B 之前,我们首先通过添加 n 个为 0 的高阶系数将它们的度界加倍到 2n。因为向量有 2n 个元素,我们使用“复数 2nth 单位根”,表示为由 W2n (omega 2n)。我们假设 n 是 2 的幂;我们总是可以通过添加高阶零系数来满足这个要求。
快速傅立叶变换
这是解决这个问题的分而治之的策略。
- 分别使用 A(x) 的偶指数和奇指数系数,定义两个新的度数为 n/2 的多项式
- 评估 A(x) 的问题简化为评估点处的度数限制 n/2 多项式 A0(x) 和 A1(x)
- 现在结合结果
列表不包含 n 个不同的值,但包含 n/2 个复数 n/2 个单位根。多项式 A0 和 A1 在第 n 个复数单位根处递归计算。子问题与原始问题具有完全相同的形式,但大小只有其一半。所以形成的递归是T(n) = 2T(n/2) + O(n),即复杂度O(nlogn)。
Algorithm
1. Add n higher-order zero coefficients to A(x) and B(x)
2. Evaluate A(x) and B(x) using FFT for 2n points
3. Pointwise multiplication of point-value forms
4. Interpolate C(x) using FFT to compute inverse DFT
递归FFT的伪代码
Recursive_FFT(a){ n = length(a) // a 是输入系数向量,如果 n = 1 则返回 a // wn 是原理复数的 n 次单位根。 wn = e^(2*pi*i/n) w = 1 // 偶数索引系数 A0 = (a0, a2, …, an-2 ) // 奇数索引系数 A1 = (a1, a3, .. ., an-1 ) y0 = Recursive_FFT(A0) // 局部数组 y1 = Recursive-FFT(A1) // k = 0 到 n/2 – 1 的局部数组 // y 数组存储 DFT // of给定多项式。 do y[k] = y0[k] + w*y1[k] y[k+(n/2)] = y0[k] – w*y1[k] w = w*wn return y } 上面的递归树执行-
为什么这样做?
自从,
因此,Recursive-FFT 返回的向量 y 确实是输入的 DFT
向量
#include
using namespace std;
// For storing complex values of nth roots
// of unity we use complex
typedef complex cd;
// Recursive function of FFT
vector fft(vector& a)
{
int n = a.size();
// if input contains just one element
if (n == 1)
return vector(1, a[0]);
// For storing n complex nth roots of unity
vector w(n);
for (int i = 0; i < n; i++) {
double alpha = -2 * M_PI * i / n;
w[i] = cd(cos(alpha), sin(alpha));
}
vector A0(n / 2), A1(n / 2);
for (int i = 0; i < n / 2; i++) {
// even indexed coefficients
A0[i] = a[i * 2];
// odd indexed coefficients
A1[i] = a[i * 2 + 1];
}
// Recursive call for even indexed coefficients
vector y0 = fft(A0);
// Recursive call for odd indexed coefficients
vector y1 = fft(A1);
// for storing values of y0, y1, y2, ..., yn-1.
vector y(n);
for (int k = 0; k < n / 2; k++) {
y[k] = y0[k] + w[k] * y1[k];
y[k + n / 2] = y0[k] - w[k] * y1[k];
}
return y;
}
// Driver code
int main()
{
vector a{1, 2, 3, 4};
vector b = fft(a);
for (int i = 0; i < 4; i++)
cout << b[i] << endl;
return 0;
}
Input: 1 2 3 4
Output:
(10, 0)
(-2, -2)
(-2, 0)
(-2, 2)
插值
交换 a 和 y 的角色。
用 wn^-1 替换 wn。
将结果的每个元素除以 n。
时间复杂度:O(nlogn)。
如果您希望与专家一起参加现场课程,请参阅DSA 现场工作专业课程和学生竞争性编程现场课程。