http://blog.csdn.net/whinah/article/details/6419680
从理论上讲,只要允许使用栈,所有的递归程序都可以转化成迭代。
但是并非所有递归都必须用栈,不用堆栈也可以转化成迭代的,大致有两类
- 尾递归:可以通过简单的变换,让递归作为最后一条语句,并且仅此一个递归调用。
// recursive int fac1(int n) { if (n <= 0) return 1; return n * fac1(n-1); } // iterative int fac2(int n) { int i = 0, y = 1; for (; i <= n; ++i) y *= i; return y; }
自顶向下->自底向上:对程序的结构有深刻理解后,自底向上计算,比如 fibnacci 数列的递归->迭代转化。
// recursive, top-down int fib1(int n) { if (n <= 1) return 1; return fib1(n-1) + fib1(n-2); } // iterative, down-top int fib2(int n) { int f0 = 1, f1 = 1, i; for (i = 2; i <= n; ++i) { int f2 = f1 + f0; f0 = f1; f1 = f2; } return f1; }
对于非尾递归,就必须使用堆栈。可以简单生硬地使用堆栈进行转化:把函数调用和返回的地方翻译成汇编代码,然后把对硬件 stack 的 push, pop 操作转化成对私有 stack 的 push, pop ,这其中需要特别注意的是对返回地址的 push/pop,对应的硬件指令一般是 call/ret。使用私有 stack 有两个好处:
- 可以省去公用局部变量,也就是在任何一次递归调用中都完全相同的函数参数,再加上从这些参数计算出来的局部变量。
- 如果需要得到当前的递归深度,可以从私有 stack 直接拿到,而用递归一般需要一个单独的 depth 变量,然后每次递归调用加 1。
我们把私有 stack 元素称为 Frame,那么 Frame 中必须包含以下信息:
- 返回地址(对应于每个递归调用的下一条语句的地址)
- 对每次递归调用都不同的参数
通过实际操作,我发现,有一类递归的 Frame 可以省去返回地址!所以,这里又分为两种情况:
- Frame 中可以省去返回地址的递归:仅有两个递归调用,并且其中有一个是尾递归。
// here used a function 'partition', but don't implement it tempalte<class RandIter> void QuickSort1(RandIter beg, RandIter end) { if (end - beg <= 1) return; RandIter pos = partition(beg, end); QuickSort1(beg, pos); QuickSort1(pos + 1, end); } tempalte<class RandIter> void QuickSort2(RandIter beg, RandIter end) { std::stack<std::pair<RandIter> > stk; stk.push({beg, end}); while (!stk.empty()) { std::pair<RandIter, RandIter> ii = stk.top(); stk.pop(); if (ii.second - ii.first) > 1) { RandIter pos = partition(beg, end); stk.push({ii.first, pos}); stk.push({pos + 1, ii.second}); } } }
Frame 中必须包含返回地址的递归,这个比较复杂,所以我写了个完整的示例:
- 以MergeSort为例,因为 MergeSort 是个后序过程,两个递归调用中没有任何一个是尾递归
- MergeSort3 使用了 GCC 的 Label As Value 特性,只能在 GCC 兼容的编译器中使用
- 单纯对于这个实例来说,返回地址其实只有两种,返回地址为 0 的情况可以通过判断私有栈(varname=stk)是否为空,stk为空时等效于 retaddr == 0。如果要精益求精,一般情况下指针的最低位总是0,可以把这个标志保存在指针的最低位,当然,如此的话就无法对 sizeof(T)==1 的对象如 char 进行排序了。
#include <stdio.h> #include <string.h> # if 1 #include <stack> #include <vector> template<class T> class MyStack : public std::stack<T, std::vector<T> > { }; #else template<class T> class MyStack { union { char* a; T* p; }; int n, t; public: explicit MyStack(int n=128) { this->n = n; this->t = 0; a = new char[n*sizeof(T)]; } ~MyStack() { while (t > 0) pop(); delete[] a; } void swap(MyStack<T>& y) { char* q = y.a; y.a = a; a = q; int z; z = y.n; y.n = n; n = z; z = y.t; y.t = t; t = z; } T& top() const { return p[t-1]; } void pop() { --t; p[t].~T(); } void push(const T& x) { x.print(); // debug p[t] = x; ++t; } int size() const { return t; } bool empty() const { return 0 == t; } bool full() const { return n == t; } }; #endif template<class T> struct Frame { static T* base; T *beg, *tmp; int len; int retaddr; Frame(T* beg, T* tmp, int len, int retaddr) : beg(beg), tmp(tmp), len(len), retaddr(retaddr) {} void print() const { // for debug printf("%4d %4d %d/n", int(beg-base), len, retaddr); } }; template<class T> T* Frame<T>::base; #define TOP(field) stk.top().field template<class T> bool issorted(const T* a, int n) { for (int i = 1; i < n; ++i) { if (a[i-1] > a[i]) return false; } return true; } template<class T> void mymerge(const T* a, int la, const T* b, int lb, T* c) { int i = 0, j = 0, k = 0; for (; i < la && j < lb; ++k) { if (b[j] < a[i]) c[k] = b[j], ++j; else c[k] = a[i], ++i; } for (; i < la; ++i, ++k) c[k] = a[i]; for (; j < lb; ++j, ++k) c[k] = b[j]; } template<class T> void MergeSort1(T* beg, T* tmp, int len) { if (len > 1) { int mid = len / 2; MergeSort1(beg , tmp , mid); MergeSort1(beg+mid, tmp+mid, len-mid); mymerge(tmp, mid, tmp+mid, len-mid, beg); memcpy(tmp, beg, sizeof(T)*len); } else *tmp = *beg; } template<class T> void MergeSort2(T* beg0, T* tmp0, int len0) { int mid; int cnt = 0; Frame<T>::base = beg0; MyStack<Frame<T> > stk; stk.push(Frame<T>(beg0, tmp0, len0, 0)); while (true) { ++cnt; if (TOP(len) > 1) { mid = TOP(len) / 2; stk.push(Frame<T>(TOP(beg), TOP(tmp), mid, 1)); continue; L1: mid = TOP(len) / 2; stk.push(Frame<T>(TOP(beg)+mid, TOP(tmp)+mid, TOP(len)-mid, 2)); continue; L2: mid = TOP(len) / 2; mymerge(TOP(tmp), mid, TOP(tmp)+mid, TOP(len)-mid, TOP(beg)); memcpy(TOP(tmp), TOP(beg), sizeof(T)*TOP(len)); } else *TOP(tmp) = *TOP(beg); int retaddr0 = TOP(retaddr); stk.pop(); switch (retaddr0) { case 0: return; case 1: goto L1; case 2: goto L2; } } } // This Implementation Use GCC's goto saved label value // Very similiar with recursive version template<class T> void MergeSort3(T* beg0, T* tmp0, int len0) { MyEntry: int mid; int retaddr; Frame<T>::base = beg0; MyStack<Frame<T> > stk; stk.push(Frame<T>(beg0, tmp0, len0, 0)); #define Cat1(a,b) a##b #define Cat(a,b) Cat1(a,b) #define HereLabel() Cat(HereLable_, __LINE__) #define RecursiveCall(beg, tmp, len) / stk.push(Frame<T>(beg, tmp, len, (char*)&&HereLabel() - (char*)&&MyEntry)); / continue; / HereLabel():; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // retaddr == 0 是最外层的递归调用, // 只要到达这一层时 retaddr 才为 0, // 此时就可以返回了 #define MyReturn / retaddr = TOP(retaddr); / stk.pop(); / if (0 == retaddr) { / return; / } / goto *((char*)&&MyEntry + retaddr); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ while (true) { if (TOP(len) > 1) { mid = TOP(len) / 2; RecursiveCall(TOP(beg), TOP(tmp), mid); mid = TOP(len) / 2; RecursiveCall(TOP(beg)+mid, TOP(tmp)+mid, TOP(len)-mid); mid = TOP(len) / 2; mymerge(TOP(tmp), mid, TOP(tmp)+mid, TOP(len)-mid, TOP(beg)); memcpy(TOP(tmp), TOP(beg), sizeof(T)*TOP(len)); } else *TOP(tmp) = *TOP(beg); MyReturn; } } template<class T> void MergeSortDriver(T* beg, int len, void (*mf)(T* beg_, T* tmp_, int len_)) { T* tmp = new T[len]; (*mf)(beg, tmp, len); delete[] tmp; } #define test(a,n,mf) / memcpy(a, b, sizeof(a[0])*n); / MergeSortDriver(a, n, &mf); / printf("sort by %s:", #mf); / for (i = 0; i < n; ++i) printf("% ld", a[i]); / printf("/n"); int main(int argc, char* argv[]) { int n = argc - 1; int i; long* a = new long[n]; long* b = new long[n]; for (i = 0; i < n; ++i) b[i] = strtol(argv[i+1], NULL, 10); test(a, n, MergeSort1); test(a, n, MergeSort2); test(a, n, MergeSort3); printf("All Successed/n"); delete[] a; delete[] b; return 0; }
http://itjingyingjida.blog.sohu.com/161173529.html
一个算法设计技巧:递归转化成迭代。
在某些特定的情况下,递归的效率是非常低的,必须使用迭代来实现。 下面,将通过两个经典的递归例子,来感悟这两种算法设计方法的效率问题。
1.计算n的阶乘。
学过最基本程序设计知识的同学都知道,递归处理。
#include <stdio.h>
#include<stdlib.h>
long fact(int);
int main(void)
{ int n;
printf("请输入一个非负整数n:\n");
scanf("%d",&n);
if(n<0)
printf("error:n must>0.");
else
printf("%d的阶乘为%d.\n",n,fact(n));
system("pause");
return 0;
}
//计算递归的函数
long fact(int n)
{
if(n<=0)
return -1;
else
return n*fact(n-1);
}
2.计算Fabonacci数列。
基本定义:
f(n)=0,n=0;
f(n)=1,n=1;
f(n)=1,n=2;
f(n)=f(n-1)+f(n-2),(n>2);递归定义。
递归实现:
#include <stdio.h>
//计算数列第n项
long fib(int n)
{
if (n == 1 || n == 2)
return 1;
return fib(n - 1) + fib(n - 2);
}
int main()
{
int n;
scanf("%d", &n);
printf("%ld\n", fib(n));
return 0;
}
一个程序,或者一个算法写好之后,我们自然而然地想到:好有没有更好的实现??这个算法好么??能改进么??
对于这两个程序,效率确实很低下,必须使用更优秀的方式来实现。
一.先分析Fibonacci数列的计算。
1.感性的认知。
可以看到,f(1)和f(2)被重复计算了很多次,因此效率显得很低下。
2.理性分析。
这里的工作即将集中在程序运行时间的分析上面。
假设,计算第n项需要的时间为T(n)。
由递归关系式知道:T(n) = T(n - 1) + T(n - 2),n >= 3;T(n) = 1。这里的1代表1个CPU单位时间,即CPU计算一个基本语句所需要的时间。 例如,赋值语句,比较大小等等。
先定义一个函数,叫做生成函数,也叫母函数。
(1)Stirling公式的证明过程如下(来自维基百科):
这个公式,以及误差的估计,可以推导如下。我们不直接估计n!,而是考虑它的自然对数:
这个方程的右面是积分的近似值(利用梯形法则),而它的误差由欧拉-麦克劳林公式给出:
其中Bk是伯努利数,Rm,n是欧拉-麦克劳林公式中的余项。取极限,可得:
我们把这个极限记为y。由于欧拉-麦克劳林公式中的余项Rm,n满足:
其中我们用到了大O符号,与以上的方程结合,便得出对数形式的近似公式:
两边取指数,并选择任何正整数m,我们便得到了一个含有未知数ey的公式。当m=1时,公式为:
当n趋于无穷大时,两边取极限,并利用沃利斯乘积,便可以得出ey()。因此,我们便得出斯特灵公式:
这个公式也可以反复使用分部积分法来得出,首项可以通过最速下降法得到。把以下的和
用积分近似代替,可以得出不含的因子的斯特灵公式(这个因子通常在实际应用中无关):
[编辑]收敛速率和误差估计
更加精确的近似公式为:
其中:
斯特灵公式实际上是以下级数(现在称为斯特灵级数)的第一个近似值:
当时,截断级数的误差等于第一个省略掉的项。这是渐近展开式的一个例子。它不是一个收敛级数;对于任何特殊值n,级数的准确性只在取有限个项时达到最大,如果再取更多的项,则准确性将变得越来越差。
阶乘的对数的渐近展开式也称为斯特灵级数:
在这种情况下,级数的误差总是与第一个省略掉的项同号,且最多同大小。
(3)数据分析
至此,数学分析已经臻于完美。
终于知道,自己为什么学数学了,呵呵 。
int Fibo(int n){
int a1=1,a2=1; //前两项
int an ; //第n项
if(n==1||n==2) //n <= 2返回1
return 1 ;
for(int i=3;i<n;i++)
{
an=a1+a2;
a1=a2;
a2=an;
}
return an;
}
2.计算n的阶乘
#include <stdio.h>
int main()
{
int i,n,s=1;
scanf("%d",&n);
for(i=1;i<=n;i++)
s*=i;
printf("%d!=%d\n",n,s);
return 0;
}
****不需要消解的递归
那种盲目的消解递归,不惜一切代价躲避递归,认为“递归的速度慢,为了提高速度,必须用栈或者其他的方法来消解”的说法是很片面的。如果一个递归过程用非递归的方法实现后,速度提高了,那只是因为递归做了一些无用功。假使一个递归过程必须要用栈才能消解,那么完全模拟后的结果根本就不会对速度有任何提升,只会减慢;如果你改完后速度提升了,那只证明你的递归函数写的有问题,如多了许多重复操作——打开关闭文件、连接断开数据库,而这些完全可以放到递归外面。可以在本质上是非递归的机器上实现递归过程这一事实本身就证明:为着实际目的,每一个递归程序都可以翻译成纯粹迭代的形式,但这包含着对递归栈的显式处理,而这些运算常常模糊了程序的本质,以致使它非常难以理解。
因此,是递归的而不是迭代的算法应当表述成递归过程。如汉诺塔问题等。汉诺塔问题的递归算法中有两处递归调用,并且其中一处递归调用语句后还有其他语句,因此该递归算法不是尾递归或单向递归。要把这样的递归算法转化为非递归算法,并没有提高程序运行的速度,反而会使程序变得复杂难懂,这是不可取的。也就是说,很多递归算法并不容易改写成迭代程序:它们本质上是递归的,没有简单的迭代形式。这样的递归算法不宜转化为非递归算法。
说到底,在我们选择算法时应该全面分析算法的可行性、效率、代码优化。在综合了算法的各个因素后,选择合适的算法来编写程序,这样的程序才会达到优化的效果。
四.结束语
数学,是一门艺术