如何用堆栈消除递归总结

参考文章:How to replace recursive functions using stack and while-loop to avoid the stack-overflow.

链接:How to replace recursive functions using stack and while-loop to avoid the stack-overflow - CodeProject

论文是外国文献,全英文版的。讲述了用栈消除递归的一些方法,蒻姬读了之后写篇文总结了一下~~

首先,为何要用堆栈消除递归?一方面来说,对于数据量比较大的输入,可以防止溢出;另一方面,这种方式更接近本质,我们可能会用递归轻松的写出一道题,但是我认为真正能用非递归写的话,也是一种能力。

当然,文章最后也提到了,递归函数是一个条理非常清晰,简洁明了的写法。所以在写代码时候不妨先写出递归,必要的时候再消除递归。

言归正传,用堆栈消除递归,本质上来说就是个模拟过程。文章对这个过程写得很清楚了,下面我再总结一下十条规则:

1.创建一个结构体,包含三个变量:n//输入 test//局部变量 stage//状态量(执行阶段)

2.创建返回值变量,并且初始化返回值,表示返回函数在递归中的作用。(void类型可以忽略)

3.创建栈。

4.初始化数据,并且压入栈。

5.创建循环,条件为栈非空,每一次循环执行完都将被执行的结构体弹出栈。

6.这一点很重要,stage分为调用阶段和调用返回后执行的阶段,因为两个阶段所执行的程序不同。如果有两次调用,需要用三个状态。总之,根据递归调用,可以分为不同状态,毕竟调用就是一个新的代码块。

7.根据stages执行不同状态(废话。。。)

8.如果递归函数有返回值,需要进行存储。

9.返回关键词在非递归函数中写作continue(视情况而定)

10.最后一点也是整个算法的重头戏。步骤是:如果是执行的递归函数,首先改变结构体的状态,将其压入栈中。然后创建新的结构体并初始化数据,压入栈中。

我以我的理解解释一下:在递归函数中,如果再次申请调用,本次调用并没有执行完,当被调函数执行完后才会执行后面的语句。这时在非递归中,可以理解为状态改变了,这就是改变状态还要压入栈的原因:后面的状态以后还会执行。

模板代码:

int SomeFunc(int n, int &retIdx) //递归函数
{
   ...
   if(n>0)
   {
      int test = SomeFunc(n-1, retIdx);
      test--;
      ...
      return test;
   }
   ...
   return 0;
}
C++
// Conversion to Iterative Function
int SomeFuncLoop(int n, int &retIdx)
{
     // (First rule)
    struct SnapShotStruct {
       int n;        // - parameter input 输入
       int test;     // - local variable that will be used 函数内局部变量
                     //     after returning from the function call
                     // - retIdx can be ignored since it is a reference.
       int stage;    // - Since there is process needed to be done 
                     //     after recursive call. (Sixth rule)执行阶段
    };
    // (Second rule)
    int retVal = 0;  // initialize with default returning value 返回值变量
    // (Third rule)
    stack snapshotStack;
    // (Fourth rule)
    SnapShotStruct currentSnapshot; //初始化
    currentSnapshot.n= n;          // set the value as parameter value
    currentSnapshot.test=0;        // set the value as default value
    currentSnapshot.stage=0;       // set the value as initial stage
    snapshotStack.push(currentSnapshot);
    // (Fifth rule)
    while(!snapshotStack.empty())
    {
       currentSnapshot=snapshotStack.top();
       snapshotStack.pop();
       // (Sixth rule)
       switch( currentSnapshot.stage)
       {
       case 0: //调用函数阶段
          // (Seventh rule)
          if( currentSnapshot.n>0 )
          {
             // (Tenth rule)
             currentSnapshot.stage = 1;            // - current snapshot need to process after 改变状态
                                                   //     returning from the recursive call
             snapshotStack.push(currentSnapshot);  // - this MUST pushed into stack before 这次调用还没执行完
                                                   //     new snapshot!
             // Create a new snapshot for calling itself
             SnapShotStruct newSnapshot;
             newSnapshot.n= currentSnapshot.n-1;   // - give parameter as parameter given
                                                   //     when calling itself
                                                   //     ( SomeFunc(n-1, retIdx) )
             newSnapshot.test=0;                   // - set the value as initial value
             newSnapshot.stage=0;                  // - since it will start from the 
                                                   //     beginning of the function, 
                                                   //     give the initial stage
             snapshotStack.push(newSnapshot); //下次调用压入栈
             continue;
          }
          ...
          // (Eighth rule)
          retVal = 0 ;
          
          // (Ninth rule)
          continue;
          break; 
       case 1: 
          // (Seventh rule)
          currentSnapshot.test = retVal;
          currentSnapshot.test--;
          ...
          // (Eighth rule)
          retVal = currentSnapshot.test;
          // (Ninth rule)
          continue;
          break;
       }
    }
    // (Second rule)
    return retVal;
} 

以上即为文章所给出的模拟递归函数的通法。但是并不意味着每个递归函数转化为非递归时都需要这样模拟。其实大部分情况下,我们都可以用递推来实现。比如求阶乘,斐波那契数列这样的,以及大多数动态规划问题,可以写出递推式的,可以直接将递归转化为递推式,用循环递推即可,不需要用堆栈。

按照上面的思路,下面我用非递归写汉诺塔问题:

递归函数形式:

#include
using namespace std;
void move(int n,char x,char y,char z)
{
	if(n==1) cout<"<"<

测试运行结果:

如何用堆栈消除递归总结_第1张图片

非递归形式:

 思路:比较简单的一类模拟,首先,函数没有返回值,不需要返回值变量。另外,递归函数有两次调用,所以有三个阶段。

 

#include
#include
using namespace std;
struct node{
	int n;
	char x;
	char y;
	char z;
	int stage; //状态变量
};
void move(int n,char x,char y,char z)
{
	node currentnode;
	currentnode.n=n;
	currentnode.x=x;
	currentnode.y=y;
	currentnode.z=z;
	currentnode.stage=0;  //第一次调用初始化
	stackS;
	S.push(currentnode);
	while(!S.empty()){
		currentnode=S.top();
		S.pop();
		switch(currentnode.stage){ //判断状态
			case 0: //执行move(n-1,x,z,y);
				if(currentnode.n==1) cout<"<"<"<

 测试运行结果:

如何用堆栈消除递归总结_第2张图片

读者有兴趣可以试试用非递归写快排和归排,当然不一定用上述方法。

你可能感兴趣的:(算法,数据结构,堆栈,递归法)