FFT算法实践

快速傅里叶变换(FFT)

使用范围:多项式相乘。

傅里叶变换及逆变换

1、多项式的点表示
A(x)=a0+a1*x+a2*x2+…+ala-1*xla-1(共la项)
先代入la个x值得到la个A(x)值,用la个点<xi,Ai>(i=0、1…la-1)表示
知道这些点就能反解出A的各项系数即可用这la个点表示多项式A

2、多项式乘法的点表示
C(x)=A(x)*B(x)
<xi,A(xi)>属于A
<xi,B(xi)>属于B(这里的xi与A的是同一个)
则易得<xi,A(xi)*B(xi)>属于C,所以只要有>=C中系数数量的点就能解得C的所有系数

3、傅里叶变换的思想
代入n个x把A、B都转换成点//称为离散傅里叶变换
再得到C的点<xi,Ci>这里的x使用复数
最后用C上的点得到C的所有系数。//称为傅里叶逆变换

4、傅里叶变换的数学准备

傅里叶要用到的n个复数,不是随机找的,而是——把单位圆(圆心为原点、1为半径的圆)n等分,取这n个点(或点表示的向量)所表示的虚数,即分别以这n个点的横坐标为实部、纵坐标为虚部,所构成的虚数。
在这里插入图片描述
从点(1,0)开始(显然这个点是我们要取的点之一),逆时针将这n个点从0开始编号,第k个点对应的虚数记作ωkn(根据复数相乘时模长相乘幅角相加可以看出,ωkn是ω1n的k次方,所以ω1n被称为n次单位根)。
根据每个复数的幅角,可以计算出所对应的点/向量。ωkn对应的点/向量是
eik*2π/n
单位根的性质:
性质一:ω2k2nkn
性质二:ωk+n2n=−ωkn
摘自:https://www.cnblogs.com/RabbitHu/p/FFT.html

5、傅里叶变换证明
其核心就是单位根及其共轭的运用
令(y0,y1,…,yn−1) 为多项式A(x)=a0+a1x+…+anxn−1代入n个单位根后的n个值。
设一个多项式T(x)=y0+y1x+…+yn−1xn−1,现在我们把上面的n个单位根的倒数,即ω0n−1n,…,ω−(n−1)n作为x代入T(x), 同样得到n个值(z0,z1,…,zn−1)。
在这里插入图片描述
当j−k=0的部分的值等于n; 其它情况,等比数列求和可知它等于0
所以zk就等于n*ak
相当于又来了一次离散傅里叶变换
6、快速傅里叶变换的理论
终于到正题了。。。
A(x)=a0+a1x+…+anxn−1
首先将A(x)的每一项按照下标的奇偶分成两部分:
A(x)=(a0+a2x2+…+an−2xn−2)+(a1x+a3x3+…+an−1xn−1)
设两个多项式:
A1(x)=a0+a2x+…+an−2xn/2−1
A2(x)=a1+a3x+…+an−1xn/2−1
可得:
A(x)=A1(x2)+xA2(x2)
假设k<n/2,将x=ωkn代入:
A(ωkn)=A1(ω2kn)+ωknA2(ω2kn)=A1(ωkn/2)+ωknA2(ωkn/2)
对于A(ωk+n/2n):
A(ωk+n/2n)=A1(ω2k+nn)+ωk+n/2nA2(ω2k+nn)=A1(ωkn/2×ωnn)+ωk+n/2nA2(ωkn/2×ωnn)=A1(ωkn/2)−ωknA2(ωkn/2)
一眼下去肯定是分治递归,但那样肯定数据爆炸递归往往是这样 ,但我们还有动规,应该从底层(分到每项只有一个元素)向上递推,具体实现如下。

编程实现

1、多项式的预处理
A(x)=a0+a1*x+a2*x2+…+ala-1*xla-1(共la项)
B(x)=b0+b1*x+b2*x2+…+blb-1*xlb-1(共lb项)
C(x)=A(x)*B(x)=c0+c1*x+c2*x2+…+clc-1*xlc-1(lc<=la * lb)
要使用FFT首先得将la、lb补至n(n=2k,n>=lc),xla-1,xlb-1之后的项用0系数填。
*为了有足够数量的<x,C(x)>来解得C的所有系数,一定要n>=lc。
A(x)=a0+a1*x+a2*x2+…+an-1*xn-1
B(x)=b0+b1*x+b2*x2+…+bn-1*xn-1

    cin >> stra;
	cin >> strb;
	lena = strlen(stra);
	lenb = strlen(strb);
	while (n < lena + lenb)
		n *= 2;

2、求解单位根
预处理从w0n至wn-1n的n个单位根及其共轭

void init(cp*omg,cp*inv,int n) {
	for (int i = 0; i < n; ++i) {
		omg[i] = cp(cos(2 * pi * i / n), sin(2 * pi * i / n));
		inv[i] = ~omg[i];
	}
}

3、处理递推底层系数
先通过递归图像分析:
如果a的下标能被2整除,则排在左侧否则在右侧。
如果a的下标能被4整除,则排在左侧的左侧。
如果a的下标能被2整除不能能被4整除,则排在左侧的右侧。



故a的位置与其下标的二进制表示有关。
在这里插入图片描述
在A中的排列{a0,a1,…,an-1},ai最后的位置的二进制表示是i的二进制表示的对称数(如:n=8,3=011B,a3排在6=110B处)

int lim = 0;
	while ((1 << lim) < n)++lim;//计算二分了几层
	for (int i = 0; i < n; ++i) {
		int t = 0;
		for (int j = 0; j < lim; ++j)
			if ((i >> j) & 1)//比较二进制表示每一位是否为1
				t |= (1 << (lim - j - 1));//下标二进制的相反
		if (i < t) {//因为两个二进制数互为相反,进行交换位置
		            //i<t主要为了防止交换两次
			cp temp;
			temp = p[i];
			p[i] = p[t];
			p[t] = temp;
		}
	}

4、迭代求解A(wkn)
数组上的迭代关系如图所示:
在这里插入图片描述
可以用三重循环解得

for (int l = 2; l <= n; l *= 2) {//迭代重数
		int m = l / 2;//二分后单个新数组的数据数量
		for (int j=0;j<n;j+=l)
			for (int i = 0; i < m; i++) {//已使用蝴蝶变换优化
				cp t = omg[n / l * i] * p[j+i + m];
				p[j+i + m] = p[j+i] - t;
				p[j+i] = p[j+i]+t;
			}
	}

5、完整FFT

void init(cp*omg,cp*inv,int n) {
	for (int i = 0; i < n; ++i) {
		omg[i] = cp(cos(2 * pi * i / n), sin(2 * pi * i / n));
		inv[i] = ~omg[i];
	}
}

void FFT(cp* p, cp* omg,int n) {
	int lim = 0;
	while ((1 << lim) < n)++lim;
	for (int i = 0; i < n; ++i) {
		int t = 0;
		for (int j = 0; j < lim; ++j)
			if ((i >> j) & 1)
				t |= (1 << (lim - j - 1));
		if (i < t) {
			cp temp;
			temp = p[i];
			p[i] = p[t];
			p[t] = temp;
		}
	}
	for (int l = 2; l <= n; l *= 2) {
		int m = l / 2;
		for (int j=0;j<n;j+=l)
			for (int i = 0; i < m; i++) {
				cp t = omg[n / l * i] * p[j+i + m];
				p[j+i + m] = p[j+i] - t;
				p[j+i] = p[j+i]+t;
			}
	}
}

5、编程要点
在编程中一定会用到复数计算,c++自带的complex库有点慢,推荐自己建。
三重for里的for (int j=0;j<n;j+=l)很容易忘记j+=l。。。我健忘
在处理通过FFT得到的数据时下列处理方法不错(别忘记逆变换后要除n)

floor(a[i].real() / n + 0.5);

6、附上FFT加速的高精度乘法

#include <iostream>
#include<cmath>
#include<cstring>
const double pi = 3.1415926;
using namespace std;
class complex {
private:
	double x, y;
public:
	complex() {
		x = y = 0;
	}
	complex(double a, double b) {
		x = a;
		y = b;
	}
	double real(void) {
		return x;
	}
	void real(double a) {
		x = a;
	}
	complex operator +(const complex& a) {
		complex result;
		result.x = this->x + a.x;
		result.y = this->y + a.y;
		return result;
	}
	complex operator -(const complex& a) {
		complex result;
		result.x = this->x - a.x;
		result.y = this->y - a.y;
		return result;
	}
	complex operator *(const complex& a) {
		complex result;
		result.x = (this->x) * a.x - (this->y) * a.y;
		result.y = (this->x) * a.y + (this->y) * a.x;
		return result;
	}
	friend complex operator  ~ (const complex& a);
};
complex operator  ~ (const complex& a) {
	complex result;
	result.x = a.x;
	result.y = -a.y;
	return result;
}
typedef complex cp;



void init(cp*omg,cp*inv,int n) {
	for (int i = 0; i < n; ++i) {
		omg[i] = cp(cos(2 * pi * i / n), sin(2 * pi * i / n));
		inv[i] = ~omg[i];
	}
}

void FFT(cp* p, cp* omg,int n) {
	int lim = 0;
	while ((1 << lim) < n)++lim;
	for (int i = 0; i < n; ++i) {
		int t = 0;
		for (int j = 0; j < lim; ++j)
			if ((i >> j) & 1)
				t |= (1 << (lim - j - 1));
		if (i < t) {
			cp temp;
			temp = p[i];
			p[i] = p[t];
			p[t] = temp;
		}
	}
	for (int l = 2; l <= n; l *= 2) {
		int m = l / 2;
		for (int j=0;j<n;j+=l)
			for (int i = 0; i < m; i++) {
				cp t = omg[n / l * i] * p[j+i + m];
				p[j+i + m] = p[j+i] - t;
				p[j+i] = p[j+i]+t;
			}
	}
}

int len1, len2, n = 1;
int res[10005] = {};
cp a[10005], b[10005];
cp omg[10005], inv[10005];
char sa[10005], sb[10005];

int main()
{	

	cin >> sa;
	cin >> sb;
	len1 = strlen(sa);
	len2 = strlen(sb);
	while (n < len1 + len2)n *= 2;
	for(int i=0;i<len1;++i)
		a[i].real(sa[len1 - 1 - i] - '0');
	for(int i=0;i<len2;++i)
		b[i].real(sb[len2 - 1 - i] - '0');
	init(omg,inv,n);
	FFT(a, omg,n);
	FFT(b, omg,n);
	for (int i = 0; i < n; ++i)
		a[i] = b[i]*a[i];
	FFT(a, inv,n);
	for (int i = 0; i < n; i++) {
		res[i] += floor(a[i].real() / n + 0.5);
		res[i + 1] += res[i] / 10;
		res[i] %= 10;
	}
	if (res[len1 + len2 - 1] == 0)
		--len1;
	for (int i = len1 + len2 - 1; i >= 0; i--)
		printf("%d", res[i]);
}
发布了6 篇原创文章 · 获赞 5 · 访问量 1303

猜你喜欢

转载自blog.csdn.net/SuperRabbit007/article/details/104348325
FFT
今日推荐