如无必要,尽量使用通俗的定义
原则上只需要理解就可以。
1. 卷积的定义,计算,以及问题
1.1 卷积定义理解
数学定义
给定两个向量
a
=
(
a
0
,
a
1
,
.
.
.
a
n
)
a = (a_0,a_1,...a_n)
a=(a0,a1,...an) 和
b
=
(
b
0
,
b
1
.
.
.
b
n
)
b = (b_0,b_1...b_n)
b=(b0,b1...bn)
他们的卷积
a
∗
b
=
(
a
0
b
0
,
a
0
b
1
+
a
1
b
0
,
a
0
b
2
+
a
1
b
1
+
a
2
b
0
,
.
.
.
a
n
−
2
b
n
−
1
+
a
n
−
1
b
n
−
2
,
a
n
−
1
b
n
−
1
)
a*b = (a_0b_0, a_0b_1 +a_1b_0, a_0b_2+a_1b_1+a_2b_0,...a_{n-2}b_{n-1} + a_{n-1}b_{n-2} , a_{n-1}b_{n-1})
a∗b=(a0b0,a0b1+a1b0,a0b2+a1b1+a2b0,...an−2bn−1+an−1bn−2,an−1bn−1)即对于卷积后形成的新的向量,第k项为:
∑
(
i
,
j
)
,
i
+
j
=
k
a
i
b
j
\sum_{(i,j), i+j=k} a_ib_j
(i,j),i+j=k∑aibj
通俗理解:(只看这里就够了)
卷积的结果
- 总共有 2n-1项 , 当然这里是为了方便,如果 a有n项,b有m项,那结果就是 m+n-1 项,之后为了讨论方便,我们预设 i < m , j < n i<m, j<n i<m,j<n
- 第k项为 所有下标 i , j 加起来等于k的a,b向量的和,比如 卷积结果的第三项 c 3 = a 0 b 3 + a 1 b 2 + a 2 b 1 + a 3 b 0 c_3 = a_0b_3 + a_1b_2 + a_2b_1 + a_3b_0 c3=a0b3+a1b2+a2b1+a3b0
类比理解:
对于多项式
A
(
x
)
=
a
0
+
a
1
x
.
.
.
+
a
m
−
1
x
m
−
1
A(x) = a_0 + a_1x...+ a_{m-1}x^{m-1}
A(x)=a0+a1x...+am−1xm−1 和
B
(
x
)
=
b
0
+
b
1
x
.
.
.
+
b
n
−
1
x
n
−
1
B(x) = b_0 + b_1x...+ b_{n-1}x^{n-1}
B(x)=b0+b1x...+bn−1xn−1
他们相乘的结果
C
(
x
)
=
A
(
x
)
B
(
x
)
C(x) = A(x)B(x)
C(x)=A(x)B(x)的系数向量就是
A
(
x
)
A(x)
A(x)与
B
(
x
)
B(x)
B(x)的系数向量卷积
1.2 卷积的计算
为方便讨论,我们只考虑等长向量 m=n 的情况
从1.1的定义我们知道了,对于卷积结果的第k项,我们计算
∑
(
i
,
j
)
,
i
+
j
=
k
a
i
b
j
\sum_{(i,j), i+j=k} a_ib_j
(i,j),i+j=k∑aibj
让我们做一下算法分析:
结果总共有2n-1项,第一项我们要进行1次乘法(0,0),第二项 2次(0,1),(1,0),第n项 n次(0,n-1)…(n-1, 0) , 第2n-1项 2n-1次。
平均下来,对于每一项,我们都要计算n次乘法, 总共有2n-1项,即卷积的计算需要
O
(
n
2
)
O(n^2)
O(n2)的时间。
但是明明输入和输出都只有O(n)的规模, 我们能不能找到第一个更好的方法优化这个算法使其小于
O
(
n
2
)
O(n^2)
O(n2)呢?
1.2.1 快速傅里变换叶FFT
FFT只需要 O ( n l o g n ) O(nlogn) O(nlogn)次算术运算便可以完成两个向量的计算。
根据前文给出的类比理解我们可以这样理解:
我们把向量
a
=
(
a
0
,
a
1
,
.
.
.
a
n
)
a = (a_0,a_1,...a_n)
a=(a0,a1,...an) 和
b
=
(
b
0
,
b
1
.
.
.
b
n
)
b = (b_0,b_1...b_n)
b=(b0,b1...bn)理解成多项式
A
(
x
)
=
a
0
+
a
1
x
.
.
.
+
a
m
−
1
x
m
−
1
A(x) = a_0 + a_1x...+ a_{m-1}x^{m-1}
A(x)=a0+a1x...+am−1xm−1 和
B
(
x
)
=
b
0
+
b
1
x
.
.
.
+
b
n
−
1
x
n
−
1
B(x) = b_0 + b_1x...+ b_{n-1}x^{n-1}
B(x)=b0+b1x...+bn−1xn−1
则他们相乘的结果
C
(
x
)
=
A
(
x
)
B
(
x
)
=
c
0
+
c
1
x
.
.
.
+
c
2
n
−
1
x
2
n
−
1
C(x) = A(x)B(x)= c_0 + c_1x...+ c_{2n-1}x^{2n-1}
C(x)=A(x)B(x)=c0+c1x...+c2n−1x2n−1的系数向量
c
=
(
c
0
,
c
1
,
.
.
.
c
2
n
−
1
)
c = (c_0,c_1,...c_{2n-1})
c=(c0,c1,...c2n−1)正好就是
a
∗
b
a * b
a∗b
换句话说,我们想知道卷积的结果
c
c
c ,我们只需要知道
C
(
x
)
C(x)
C(x),
那我们的思路就很清晰了,求
A
(
x
)
B
(
x
)
A(x)B(x)
A(x)B(x)
求解多项式A(x)B(x)
首先,回想起一个关于多项式的基本事实:
任何d阶多项式可以由它在任意一组d+1个或者更多的点上的值重新构造出来,这叫做 多项式插值
也就是说如果要确定m-1阶多项式
A
(
x
)
=
a
0
+
a
1
x
.
.
.
+
a
m
−
1
x
m
−
1
A(x) = a_0 + a_1x...+ a_{m-1}x^{m-1}
A(x)=a0+a1x...+am−1xm−1, 需要导入至少m个x值。
就像如果想求解2元一次方程ax+b=0,至少需要代入2个x值,列出两个方程式才可以解出来a和b.
所以我们要确定
C
(
x
)
=
A
(
x
)
B
(
x
)
C(x) = A(x)B(x)
C(x)=A(x)B(x)
我们就要得到
C
(
x
1
)
,
C
(
x
2
)
.
.
.
.
.
.
C
(
x
2
n
)
C(x_1) , C(x_2)......C(x_{2n})
C(x1),C(x2)......C(x2n)
也就是
A
(
x
1
)
,
A
(
x
2
)
.
.
.
.
.
.
A
(
x
2
n
)
A(x_1) , A(x_2)......A(x_{2n})
A(x1),A(x2)......A(x2n)和
B
(
x
1
)
,
B
(
x
2
)
.
.
.
.
.
.
B
(
x
2
n
)
B(x_1) , B(x_2)......B(x_{2n})
B(x1),B(x2)......B(x2n)
所以设计算法
- 选择2n个值 x j = ( x 0 , x 1 , . . . x 2 n ) x_j = (x_0,x_1,...x_{2n}) xj=(x0,x1,...x2n), 然后分别求 A ( x j ) A(x_j) A(xj)和 B ( x j ) B(x_j) B(xj)
- 计算2n个 C ( x j ) = A ( x j ) B ( x j ) C(x_j) = A(x_j)B(x_j) C(xj)=A(xj)B(xj)
- 利用2n个 C ( x i ) C(x_i) C(xi)和多项式插值求出多项式 C ( x ) C(x) C(x),系数向量即为卷积结果
分析算法:
第一步: 2n * n * 2 次乘法计算, 即O(n^2)运算
第二步:O(n)次运算
第三步:利用多项式插值求多项式是O(nlogn)次运算,这个我们暂且不提
可以看到,算法仍然被第一步限制在了O(n^2), 怎么解决?
我们注意到,这2n个x值是相互独立的,我们可以找到一组密切相关的2n个x值使得求值工作可以被继承。
单位1的复数根
我们可以把一个复数表示成在复平面上的一个用极坐标表示的点
r
e
θ
i
re^{\theta i}
reθi其中
e
π
i
=
−
1
e^{\pi i}=-1
eπi=−1
对于一个多项式方程
x
k
=
1
x^k=1
xk=1,存在k个不同的复根,即
w
j
,
k
=
e
2
π
j
i
k
w_{j,k} = e^{\frac {2\pi ji}{k}}
wj,k=ek2πji for j = 0,1,2…k-1
我们把这k个根叫做单位1的k次方根。
同样的道理,我们把第一步中的2n个值选为 单位1的2n次方跟,即
x
j
=
w
j
,
2
n
=
e
2
π
j
i
2
n
=
e
π
j
i
n
x_j = w_{j,2n} = e^{\frac {2\pi ji}{2n}} = e^{\frac {\pi ji}{n}}
xj=wj,2n=e2n2πji=enπji for j = 0,1,2…2n-1
然后再来计算
A
(
x
i
)
A(x_i)
A(xi)
为了达到
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn),我们采用分治递归的方法,那怎么分?
我们定义
A
e
v
e
n
(
x
)
=
a
0
+
a
2
x
+
a
4
x
2
+
.
.
.
+
a
n
−
2
x
n
−
2
2
A_{even} (x) = a_0 + a_2x + a_4x^2 + ...+a_{n-2}x^{\frac {n-2}{2}}
Aeven(x)=a0+a2x+a4x2+...+an−2x2n−2和
A
o
d
d
(
x
)
=
a
1
+
a
3
x
+
a
5
x
2
+
.
.
.
+
a
n
−
1
x
n
−
2
2
A_{odd} (x) = a_1 + a_3x + a_5x^2 + ...+a_{n-1}x^{\frac {n-2}{2}}
Aodd(x)=a1+a3x+a5x2+...+an−1x2n−2
这样我们便可以拆分合并
A
(
x
)
=
A
e
v
e
n
(
x
2
)
+
x
A
o
d
d
(
x
2
)
A(x) = A_{even} (x^2) + xA_{odd} (x^2)
A(x)=Aeven(x2)+xAodd(x2)
我们代入某一个单位根
w
j
,
2
n
w_{j,2n}
wj,2n求值,我们有:
A
(
w
j
,
2
n
)
=
A
e
v
e
n
(
w
j
,
2
n
2
)
+
w
j
,
2
n
A
o
d
d
(
w
j
,
2
n
2
)
A(w_{j,2n}) =A_{even}(w_{j,2n}^2) + w_{j,2n}A_{odd}(w_{j,2n}^2)
A(wj,2n)=Aeven(wj,2n2)+wj,2nAodd(wj,2n2)
,我们注意到
w
j
,
2
n
2
w_{j,2n}^2
wj,2n2,其实也是这2n个单位根之一(
w
j
,
2
n
2
=
1
w_{j,2n}^2 = 1
wj,2n2=1),所以
A
(
w
j
,
2
n
2
)
A(w_{j,2n}^2)
A(wj,2n2)的求解在之前的递归中早就已经求过了,我们只需要获取它并且带到式子里面即可。这样,计算$
A
(
w
j
,
2
n
)
A(w_{j,2n})
A(wj,2n)的过程就变成了:
- 递归求解 A e v e n ( w j , 2 n 2 ) A_{even}(w_{j,2n}^2) Aeven(wj,2n2)和 A o d d ( w j , 2 n 2 ) A_{odd}(w_{j,2n}^2) Aodd(wj,2n2)
- O ( n ) O(n) O(n)次数得到 A ( w j , 2 n ) A(w_{j,2n}) A(wj,2n)
我们可以得到
T
(
n
)
≤
2
T
(
n
2
)
+
O
(
n
)
T(n) \leq 2T(\frac {n}{2}) + O(n)
T(n)≤2T(2n)+O(n)
显然,第一步的得到了需要的
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
因此整体快速傅里叶的算法只需要 O ( n l o g n ) O(nlogn) O(nlogn)次算术运算即可实现两个向量卷积的快速运算。