1、算法设计原理分析

把傅里叶变换公式进行拆分,按奇偶进行分组,使之转外为两个T(n/2)的计算式。

这样就可以找到了递归特性,可以一直拆分下去,直到一组只有一个数值。递归算法实现则可以为在每次递归中对偶数组进行再次划分进行递归,同样对奇数组进行再次划分进行递归。

其次利用傅里叶变化的周期性为N/2和对称性,可以推导出公式X(k)=G(k)+W*H(k),和X(K+n/2)= G(k)-W*H(k) ,所以每次只需要计算一半的计算量。其中系数W可以采用欧拉公式展开计算。

非递归实现则可以参考蝶形算法,由图弄清楚算法具体实现过程。

总共进行了log2(N)次蝶形,作为最外层循环条件,重点在于需要在进行迭代之前,对原数组进行重新排序,得到递归法最底层的排序结果,从而就可以从重排后的数组开始计算,第二层循环条件为次蝶形层中的奇偶组对数,最里层的循环条件为当前层的n值。

2、程序设计

递归实现:

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#define PI 3.1415926535
typedef struct Complex               //定义复数结构体
{double real;double image;
} COMPLEX;void FastFT(COMPLEX buf[], COMPLEX out[], int N, int step)
{int i;COMPLEX t;double a, b, c, d, w;if (step < N)              //每递归一次,step*2,递归层数为log2(N){FastFT(out, buf, N, step * 2);FastFT(out + step, buf + step, N, step * 2);for (i = 0; i < N; i += 2 * step){w = -PI * i / N;  //系数的指数值a = cos(w);b = sin(w);    //欧拉公式转换c = out[i + step].real; d = out[i + step].image;  //c,d表示奇数组的第i个数的实数部分和虚数部分t.real = a * c - b * d;   //权重系数乘以奇数组数值t.image = b * c + a * d;  //t表示奇数组的第i个数乘以系数后的值buf[i / 2].real = out[i].real + t.real;buf[i / 2].image = out[i].image + t.image; //求出递归返回后d对应位置上的值buf[(i + N) / 2].real = out[i].real - t.real;buf[(i + N) / 2].image = out[i].image - t.image; //由傅里叶的对称性可以求出后一半对应位置的值}}
}int fft(COMPLEX buf[], int n)
{int i;COMPLEX *out = (COMPLEX *)malloc(n * sizeof(COMPLEX));  //开辟一个和原数组等大的数组,用于存储计算奇数组的值的变化if (out == NULL){return 0;}for (i = 0; i < n; i++){out[i].real = buf[i].real;out[i].image = buf[i].image;}FastFT(buf, out, n, 1);free(out);out = NULL;return 1;
}int main()
{#define COUNT 8COMPLEX buf[COUNT] = { {1.0, 0.0}, {2.0, 0.0}, {3.0, 0.0}, {4.0, 0.0},{5.0, 0.0}, {6.0, 0.0}, {7.0, 0.0} ,{8.0, 0.0} };fft(buf, COUNT);for(int i=0; i<COUNT;i++)printf("(%.4f,%.4fi)\n", buf[i].real, buf[i].image);return 0;
}

非递归方式实现(蝶形算法):

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#define PI 3.1415926535typedef struct Complex               //定义复数结构体
{double real;double image;
} COMPLEX;void RaderReverse(COMPLEX*arr, int N) //定义对数组进行重排的函数
{     int i, j;COMPLEX temp;//第一个和最后一个数位置不变,故不处理for(i=1,j=N/2; i<N/2; i+=2,j+=2) {temp.real = arr[j].real;temp.image = arr[j].image;arr[j].real = arr[i].real;arr[j].image = arr[j].image;arr[i].real = temp.real;arr[i].image = temp.image;}
}void fft(COMPLEX *x,int n) {int i=0,j=0,k=0,l=0;COMPLEX up,down,product;double a,b,c,d,w; //w是系数 RaderReverse(x,n); //重排找到蝶形算法最底层 for(i=0; i<log(n)/log(2); ++i)  /*log(n)/log(2) 级蝶形运算 stage */  {l = 1<<i;   //当l前层的n/2值 for(j=0;j<n;j+= 2*l)    //当前层总共有n/(2*l)组奇偶对              /*一组蝶形运算 group,每组group的蝶形因子乘数不同*/ {for(k=0;k<l;++k)   //k用于每对奇偶对的计算  /*一个蝶形运算 每个group内的蝶形运算的蝶形因子乘数成规律变化*/  {w=-PI*k/l;    //系数w=-2*PI*k/(2*l)a = cos(w);b = sin(w);    //欧拉公式转换 c = x[j+k+l].real;  d = x[j+k+l].image;  //c,d表示奇数组的第i个数的实数部分和虚数部分 product.real=a * c - b * d; product.image=b * c + a * d;up.real= x[j+k].real + product.real;up.image= x[j+k].image + product.image;down.real= x[j+k].real - product.real;down.image= x[j+k].image - product.image;x[j+k].real= up.real;x[j+k].image= up.image;x[j+k+l].real= down.real;x[j+k+l].image= down.image;}}}
}
int main()
{#define COUNT 8COMPLEX buf[COUNT] = { {1.0, 0.0}, {2.0, 0.0}, {3.0, 0.0}, {4.0, 0.0},{5.0, 0.0}, {6.0, 0.0}, {7.0, 0.0} ,{8.0, 0.0} }; fft(buf, COUNT);for(int i=0; i<COUNT;i++)printf("(%.4f,%.4fi)\n", buf[i].real, buf[i].image);return 0;
}

3、运行结果

注意:变换数组长度需要为2的整数,这样才可以使分层恰好合适。

 

4、算法分析(复杂度)

可以看出,傅里叶变换约N2次乘法和N2次加法。当N较大时,这个计算量是很大的。利用WN的对称性和周期性,将N点傅里叶变换N/2点的傅里叶变换,/2点DFT总的计算量只是原来的一半,即(N/2)2+(N/2)2=N2/2,这样可以继续分解下去,将N/2再分解为N/4点傅里叶变换等。对于N=偶数点的傅里叶变换都可以分解为2点的傅里叶变换,这样其计算量可以减少为(N/2)log2(N)次乘法和Nlog2(N)次加法。所以递归傅里叶变换的时间复杂度为O(Nlog2(N)),非递归实现的乘法与加法次数也是一样的,所以非递归实现的傅里叶变换的时间复杂度也为O(Nlog2(N)),递归与非递归实现不同之处,在于递归开辟栈来存储递归所需空间,空间复杂度大于非递归实现方式。

感悟:

傅里叶变换算法真的比较难理解,学习这个算法也花了比较就的时间去理解消化,通过傅里叶变换,可以实现时域频域之间的转换。递归方式实现傅里叶变换需明确分组方式是按奇偶分组,计算的核心在于应用傅里叶变换的周期性和对称性,利用推导公式X(k)=G(k)+W*H(k),和X(K+n/2)= G(k)-W*H(k)进行相应的计算,在写递归代码时,开始我认为每次递归都需要重新保存每层的计算结果X(k),尝试了许久后,我发现我的判断、循环条件总有问题,奇偶数组上的数值没对应上,后来我打算不改变数值在原数组中的位置,通过改变部长step来实现奇偶的分组,计算结果保存到相应的位置上。

非递归方式实现,结合蝶形算法的图来理解,就比较容易理解,关键在于如何得到蝶形算法的最低层的数组排序。

傅里叶变换算法c语言实现(递归与非递归)相关推荐

  1. 树的递归与非递归遍历算法

    树的递归与非递归遍历算法 树的递归与非递归遍历算法 树的遍历 实例 树遍历的口诀 树的递归遍历代码 树的先序遍历 树的中序遍历 树的后序遍历 递归遍历思想 树的非递归遍历 树的先序非递归遍历 先序遍历 ...

  2. C语言实现二叉树的非递归遍历

    C语言实现二叉树的非递归遍历: 代码解释: 非递归前序遍历:1> 首先建立一个二维指针,用来存储每个结点的地址,定义栈顶指针top,初始值为-1,并将根结点存入栈中,top++:2> 进入 ...

  3. 树与二叉树的深度优先与广度优先算法(递归与非递归)

    本博客前面文章已对树与二叉树有过简单的介绍,本文主要是重点介绍有关二叉树的一些具体操作与应用 阅读本文前,可以先参考本博客 各种基本算法实现小结(三)-- 树与二叉树   和  各种基本算法实现小结( ...

  4. 九十五、二叉树的递归和非递归的遍历算法模板

    @Author:Runsen 刷Leetcode,需要知道一定的算法模板,本次先总结下二叉树的递归和非递归的遍历算法模板. 二叉树的四种遍历方式,前中后加上层序遍历.对于二叉树的前中后层序遍历,每种遍 ...

  5. 二叉树创建及遍历算法(递归及非递归)(转)

    //二叉树处理头文件 //包括二叉树的结构定义,二叉树的创建,遍历算法(递归及非递归), /* 作者:成晓旭 时间:2001年10月7日(18:49:38-20:00:00) 内容:完成二叉树创建,二 ...

  6. C语言/C++常见习题问答集锦(六十四) 之兔子繁殖(递归与非递归)

    C语言/C++常见习题问答集锦(六十四) 之兔子繁殖(递归与非递归) 程序之美 1.C语言,计算1乘以3×5*-乘n的积,并输出结果,n值由键盘接收. #include<stdio.h>v ...

  7. c语言中fact函数怎么调用,C语言程序题: 1、编写一个求n!的函数fact(n),要求fact函数分别用递归和非递归两种方法实现...

    点击查看C语言程序题: 1.编写一个求n!的函数fact(n),要求fact函数分别用递归和非递归两种方法实现具体信息 答:int fac(int n) //非递归{int f=1; for(;n;) ...

  8. (伪代码)树的前中后遍历和层次遍历算法实现(考研适用,递归和非递归)

    文章目录 前言 一.递归实现树的遍历 二.非递归实现 层次遍历 总结 前言 2022考研初试结束,总结了一些考研中基本常用算法.这篇主要是关于树的前中后遍历,递归实现和非递归实现两种,现在很多自命题在 ...

  9. C语言实现二叉树前序中序后序递归与非递归遍历、层次遍历、二叉树带权路径长度

    用C语言实现了二叉树的初始化,循环队列与顺序栈的建立. 利用递归与非递归方式实现前序遍历.中序遍历.后序遍历. 利用队列实现了层次遍历. 求得了二叉树带权路径长度 #include<stdio. ...

最新文章

  1. LeetCode简单题之数组异或操作
  2. 用户至上-阿里马马篇
  3. Knative 初体验:Build Hello World
  4. python装饰器是什么意思_这个python 装饰器代码是什么意思?
  5. python运行的原理_Python运行机制(转)
  6. c#开发Mongo笔记第五篇
  7. 【软件测试】单元测试的主要任务不包括全局数据结构
  8. 怎样让jquery mobile 的footer/header 固定?
  9. linux 系统下安装中文输入法
  10. 韩顺平java作业题_韩顺平java作业
  11. 深入浅出MySQL++数据库开发、优化与管理维护+第2版
  12. 史上最全ActiveX控件下载大全
  13. 评分卡模型分数转换整个流程
  14. 黑马 Docker 笔记
  15. vast显示不出来服务器,VAST实用小知识?
  16. Kali 无线网卡无法连接到网络
  17. 快鲸公寓管理系统:职业房东、公寓运营商的共同选择
  18. Android 亮度调节
  19. 音频转化mp3 ,到底选vbr还是cbr
  20. (C#)Windows Shell 外壳编程系列总结

热门文章

  1. 【网络】IP地址和静态路由
  2. Linux - uptime命令平均负载详解
  3. ES6初学知识集锦1
  4. 微信小程序涉嫌通过中断用户体验、限制用户操作的方式,收集与服务无关的用户个人信息,包括但不限于,手机号、
  5. 都市崛起?多本都市网络小说入围第四届橙瓜网络文学奖前十
  6. 2022.02.21_HTML+CSS学习总结_HTML基础
  7. 企业如何申请CCRC认证?CCRC认证证书查询官网是什么?
  8. 极坐标变换:∫e^(-x^2)dx积分求解
  9. 背景橡皮擦,通道抠图
  10. (附源码)springboot线上作业提交系统 毕业设计 220946