多項式乘法
理解傅里葉變換首先要從多項式乘法開始。
多項式的係數表示和點值表示
多項式的係數表示
存在多項式
則稱式(2)爲多項式(1)的係數表示法,係數表示法下多項式乘法的算法複雜度爲O(n2)。
多項式的點值表示
任意一個n階多項式函數可由n+1個點來確定。
點值表示法下多項式乘法的算法複雜度爲O(n)
將”係數”表示法轉化爲“離散的點”稱爲離散傅里葉變換。
將“離散的點”還原爲”係數”表示法稱爲離散傅里葉反變換。
如何求一個多項式的點值表示?
帶入n+1個x,分別求的f(x),求一次f(x)的時間複雜度爲O(n),所以總的時間複雜度爲O(n2).
利用複數的性質,可以簡化計算。
複數
複數w滿足wn=1,則稱w是n次單位根。W的n次單位根有n個。
W的8次單位根和4次單位根。
複數的單位根存在如下性質:
複數使用結構體表示
typedef struct {
double real;
double img;
}complex;
FFT離散傅里葉變換
將係數表示法轉化爲點值表示法稱爲離散傅里葉變換,核心是需要找到n+1個點來表示n階多項式函數。
多項式分解爲奇偶次數項
至此,已經可以通過第一輪蝴蝶變換求得所有的f(x),且要帶入的值減少了一半。
void initW(complex *W, int n)
{
int i;
for (i = 0; i<n; i++)
{
W[i].real = cos(2 * PI / n*i);
W[i].img = -1 * sin(2 * PI / n*i);
}
}
也就是說,每一層對於一個確定的m,用對應的兩個值可以更新出當前的值。這個操作稱爲蝴蝶變換。
遞歸調用樹
如果自底向上觀察這顆樹,將初始向量按照葉子的位置預先重排好的話,就可以實現自底向上一步步合併結果。
觀察重排的規律,發現重排之後序列的下標爲原序列下標二進制數的翻轉。
void bit_reverse(int n, complex *x)
{
complex t;
for (int i = 0, j = 0; i != n; ++i)
{
if (i > j) {
t = x[i];
x[i] = x[j];
x[j] = t;
}
for (int l = n >> 1; (j ^= l) < l; l >>= 1);
}
}
在用代碼實現的過程中,輸入和輸出都可以理解爲長度爲n的數組,n爲2的p次方,不夠的使用0補,所以需要將數據轉化到複數域,FFT變換結束後再轉換到實數域。
在用代碼實現的過程中,輸入和輸出都可以理解爲長度爲n的數組,n爲2的p次方,不夠的使用0補,所以需要將數據轉化到複數域,FFT變換結束後再轉換到實數域。
void fft(float *input, unsigned int n)
{
int i = 0, j = 0, k = 0, l = 0;
complex up, down, product;
complex *data = malloc(sizeof(complex) * n);
for (j = 0; j < n; j++)
{
data[j].real = input[j];
data[j].img = 0;
}
bit_reverse(n, data);
for (i = 0; i< log(n) / log(2); i++)
{
l = 1 << i;
for (j = 0; j<n; j += 2 * l)
{
for (k = 0; k<l; k++)
{
product.real = data[j + k + l].real * W[n*k / 2 / l].real
- data[j + k + l].img * W[n*k / 2 / l].img;
product.img = data[j + k + l].real * W[n*k / 2 / l].img
+ data[j + k + l].img * W[n*k / 2 / l].real;
up.real = data[j + k].real + product.real;
up.img = data[j + k].img + product.img;
down.real = data[j + k].real - product.real;
down.img = data[j + k].img - product.img;
data[j + k] = up;
data[j + k + l] = down;
}
}
}
for (j = 0; j < n; j++)
{
input[j] = sqrt(data[j].real * data[j].real + data[j].img * data[j].img);
}
}