如何用堆栈消除递归总结

参考文章: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<SnapShotStruct> 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<iostream>
using namespace std;
void move(int n,char x,char y,char z)
{
	if(n==1) cout<<x<<"->"<<z<<endl;
	else{
		move(n-1,x,z,y);
		cout<<x<<"->"<<z<<endl;
		move(n-1,y,x,z);
	}
}
int main()
{
	int n;
	scanf("%d",&n);
	move(n,'A','B','C');
	return 0;
}

测试运行结果:

非递归形式:

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

 

#include<iostream>
#include<stack>
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;  //第一次调用初始化
	stack<struct node>S;
	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<<currentnode.x<<"->"<<currentnode.z<<"\n";
				else{
					currentnode.stage=1; //下一个状态
				    S.push(currentnode); //记得压入栈,因为该调用还未执行完
				    node newnode;  //新的调用
				    newnode.n=currentnode.n-1;
	                newnode.x=currentnode.x;
	                newnode.y=currentnode.z;
	                newnode.z=currentnode.y;
	                newnode.stage=0;
	                S.push(newnode);
				}
	            break;
	        case 1:
	            currentnode.stage=2;
	            S.push(currentnode); //同理
	            cout<<currentnode.x<<"->"<<currentnode.z<<"\n";
	            break;
	        case 2: //执行move(n-1,y,x,z);
	        	if(currentnode.n==1) cout<<currentnode.x<<"->"<<currentnode.z<<"\n";
	        	else{
	        		node newnode; //已经到最后一步不用再将current压入栈
	        		newnode.n=currentnode.n-1;
	                newnode.x=currentnode.y;
	                newnode.y=currentnode.x;
	                newnode.z=currentnode.z;
	                newnode.stage=0;
	                S.push(newnode);
				}
	            break;
		}
	}
}
int main()
{
	int n;
	scanf("%d",&n);
	move(n,'A','B','C');
	return 0;
}

 测试运行结果:

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

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值