http://blog.csdn.net/whinah/article/details/6419680

从理论上讲,只要允许使用栈,所有的递归程序都可以转化成迭代。

但是并非所有递归都必须用栈,不用堆栈也可以转化成迭代的,大致有两类

  1. 尾递归:可以通过简单的变换,让递归作为最后一条语句,并且仅此一个递归调用。
// 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 有两个好处:

  1. 可以省去公用局部变量,也就是在任何一次递归调用中都完全相同的函数参数,再加上从这些参数计算出来的局部变量。
  2. 如果需要得到当前的递归深度,可以从私有 stack 直接拿到,而用递归一般需要一个单独的 depth 变量,然后每次递归调用加 1。

我们把私有 stack 元素称为 Frame,那么 Frame 中必须包含以下信息:

  1. 返回地址(对应于每个递归调用的下一条语句的地址)
  2. 对每次递归调用都不同的参数

通过实际操作,我发现,有一类递归的 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()。因此,我们便得出斯特灵公式:

这个公式也可以反复使用分部积分法来得出,首项可以通过最速下降法得到。把以下的和

用积分近似代替,可以得出不含的因子的斯特灵公式(这个因子通常在实际应用中无关):

[编辑]收敛速率和误差估计

 
y轴表示截断的斯特灵级数的相对误差,x轴表示所使用的项数。

更加精确的近似公式为:

其中:

斯特灵公式实际上是以下级数(现在称为斯特灵级数)的第一个近似值:

当时,截断级数的误差等于第一个省略掉的项。这是渐近展开式的一个例子。它不是一个收敛级数;对于任何特殊值n,级数的准确性只在取有限个项时达到最大,如果再取更多的项,则准确性将变得越来越差。

阶乘的对数的渐近展开式也称为斯特灵级数:

在这种情况下,级数的误差总是与第一个省略掉的项同号,且最多同大小。

 

 

 

(3)数据分析

至此,数学分析已经臻于完美。

终于知道,自己为什么学数学了,呵呵 。 微笑  偷笑 大笑   

 

两个算法的迭代版本:
1.斐波那契数列的第n项 

 

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; 
}

    

 
这两个版本都用了一个for循环来实现迭代,只有n次循环就可以完成,效率有了
很大的改善。运行时间都是O(n)。  
 
通过两个例子的分析,递归在某些场合下的效率是很低下的,因此必须使用迭代来实现。
但是,递归并不是不好,关于递归和迭代的转化问题,以下的观点来自这篇博客:
 
 

****不需要消解的递归
那种盲目的消解递归,不惜一切代价躲避递归,认为“递归的速度慢,为了提高速度,必须用栈或者其他的方法来消解”的说法是很片面的。如果一个递归过程用非递归的方法实现后,速度提高了,那只是因为递归做了一些无用功。假使一个递归过程必须要用栈才能消解,那么完全模拟后的结果根本就不会对速度有任何提升,只会减慢;如果你改完后速度提升了,那只证明你的递归函数写的有问题,如多了许多重复操作——打开关闭文件、连接断开数据库,而这些完全可以放到递归外面。可以在本质上是非递归的机器上实现递归过程这一事实本身就证明:为着实际目的,每一个递归程序都可以翻译成纯粹迭代的形式,但这包含着对递归栈的显式处理,而这些运算常常模糊了程序的本质,以致使它非常难以理解。 
因此,是递归的而不是迭代的算法应当表述成递归过程。如汉诺塔问题等。汉诺塔问题的递归算法中有两处递归调用,并且其中一处递归调用语句后还有其他语句,因此该递归算法不是尾递归或单向递归。要把这样的递归算法转化为非递归算法,并没有提高程序运行的速度,反而会使程序变得复杂难懂,这是不可取的。也就是说,很多递归算法并不容易改写成迭代程序:它们本质上是递归的,没有简单的迭代形式。这样的递归算法不宜转化为非递归算法。

 

说到底,在我们选择算法时应该全面分析算法的可行性、效率、代码优化。在综合了算法的各个因素后,选择合适的算法来编写程序,这样的程序才会达到优化的效果。

 

四.结束语

数学,是一门艺术

 

posted on 2013-06-13 17:08  Alan Yang  阅读(984)  评论(0编辑  收藏  举报