博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
递归程序的非递归实现
阅读量:7089 次
发布时间:2019-06-28

本文共 4086 字,大约阅读时间需要 13 分钟。

问题:自己管理栈来实现递归程序。找出通用的改写方法。

 

解答思路:模拟递归程序的执行过程,并每次将递归当前层的调用现场手动压入栈和弹出栈。

 

分析:

举一个简单的例子来分析递归的调用过程,如factor(n) =  n*factor(n-1).

递归程序如下:

int factor(int n){    if(n == 1)        return 1;    return n*factor(n-1);}

其执行过程如下:

由图可知,递归函数每一层的调用现场将被访问两次,并且符合“后进先出”的特点,这也是为什么要用栈。

那么我们分析stage 0这一步都做了些什么:

上图说明了在每一层,stage 0时执行的就是递归函数里,递归调用下一层的语句之前的代码。其事实也很明显,不再赘述。同样,stage 1时,由下向上执行的是递归函数里,递归调用下一层的语句之后的代码。

依此分析,将原递归函数分为如下片段:

好了,总结下上面的分析:

要将递归转为非递归,首先需要保存每一层的状态信息,包括函数参数,函数返回值等。每一层还有可能处于2个(或者更多,后面举例)不同的stage,这个信息也要记录下来。然后就是依据递归函数的调用位置(也就是入栈时刻)分别处理,在入栈前处理stage 0的代码,入栈后处理stage 1的代码。

 

解答:

现在来写对应的非递归程序:

1.首先定义一个结构体,保存每一层的递归调用现场。

结构体包含:

1)递归函数的参数;

2)如果函数有返回值,也要包括一个返回值类型。

3)一个stage变量,指示当前处于哪个stage。

struct SnapShort{    int n;    int retVal;    int stage;};

2.定义一个栈,来保存这个结构体。

stack
ss;

3.将初始状态入栈。

SnapShort currentSnap;currentSnap.n = n;currentSnap.retVal = 0;currentSnap.stage = 0;ss.push(currentSnap);

4.while循环,模拟整个递归过程。

int retVal = 0;//定义一个变量保存函数的最终返回值。while(!ss.empty()){    currentSnap = ss.top();    ss.pop();        switch(currentSnap.stage)    {    case 0:        //执行递归下一层之前的代码        if( currentSnap.n == 1)        {            retVal = 1;            continue;        }            currentSnap.stage = 1;        ss.push(currentSnap);                SnapShort newSnap;        newSnap.n = currentSnap.n-1;        newSnap.retVal = 0;        newSnap.stage = 0;                ss.push(newSnap);        break;    case 1:        //执行递归下一层之后的代码        currentSnap.retVal = currentSnap.n * retVal;               retVal = currentSnap.retVal;       break;    }} return retVal;

 

 

一点注释:

在while循环里,首先将当前状态结构体出栈,分析其处于哪个stage。

如果是stage 0,执行递归函数里属于stage 0的代码。然后将当前状态的stage改为1,并入栈。因为当前层已经处理完。然后创建下一层的状态结构体,初始化对应的值,其stage当然为0,入栈。(也就对应着递归调用下一层的语句。)

如果是stage 1,说明递归已经由下向上执行,处理stage 1对应的代码即可。

 

----------------------------------华丽的分割线------------------------------------------

例子2:关于多个stage的情况。

汉诺塔问题:托盘A上有N个从小到大的盘子,请借助B的辅助,将它们全部移动到C上。小盘子不能叠在比它大的盘子上。

递归实现很简单:

void hanoi(int n, char a, char b, char c)//初始a为’A', b为‘B’, c为‘C'{    if(n == 1)    {        cout << a << '->' << c << endl;        return;    }    hanoi(n-1, a, c, b);    cout << a << '->' << c << endl;    hanoi(n-1, b, a, c);}

非递归实现:

由递归程序可以看到,2次递归调用了下一层的自己。所以会有3个stage。如下:

对应的非递归程序如下:

void nonrecursive_hanoi(int n, char a, char b, char c){    struct SnapShort {        int n;        char a;        char b;        char c;        int stage;    };        stack
ss; SnapShort currentSnap; currentSnap.n = n; currentSnap.a = a; currentSnap.b = b; currentSnap.c = c; ss.push(currentSnap); while(!ss.empty()) { currentSnap = ss.top(); ss.pop(); switch(currentSnap.stage) { case 0: if(currentSnap.n == 1) { cout << currentSnap.a << "->" << currentSnap.c << endl; continue; } currentSnap.stage = 1; ss.push(currentSnap); SnapShort newSnap; newSnap.n = currentSnap.n - 1; newSnap.a = currentSnap.a; newSnap.b = currentSnap.c; newSnap.c = currentSnap.b; newSnap.stage = 0; ss.push(newSnap); break; case 1: cout << currentSnap.a << "->" << currentSnap.c << endl; currentSnap.stage = 2; ss.push(currentSnap); SnapShort newSnap2; newSnap2.n = currentSnap.n - 1; newSnap2.a = currentSnap.b; newSnap2.b = currentSnap.a; newSnap2.c = currentSnap.c; newSnap2.stage = 0; ss.push(newSnap2); break; case 2: break; } }}

 

总结:

本文递归转化为非递归的思路,实际上就是手动去模拟递归的执行过程。本文将递归程序划为几个stage,从而有套路的写出非递归程序,极大简化了编写非递归程序的步骤。

这儿有几个技巧:

1.如果递归函数有返回值,需要在非递归函数while循环外声明一个返回值类型的变量,用于记录最终的返回值。如果在递归程序中函数的返回值有默认值,则声明时,可用此默认值来初始化此变量。而递归程序中的return语句,就对应了非递归程序中,在while循环里的对应位置,对此变量赋值。

2.递归程序中每一句递归调用语句,对应while循环中,某层状态结构体的stage的变化。

 

备注:

本文的思路很大的借鉴了这篇论文,附链接:

转载于:https://www.cnblogs.com/tasteonbook/archive/2013/03/16/2962470.html

你可能感兴趣的文章
mysql并发插入重复数据问题的解决思路
查看>>
MySQL 5.7.x修改root默认密码(CentOS下)
查看>>
Linux下动态加载SO文件
查看>>
Mysql创建、删除用户
查看>>
我的友情链接
查看>>
MySQL-MySQL常用命令
查看>>
Linux iptraf 网络监控
查看>>
Swift::8::枚举
查看>>
ZABBIX web端 显示 server 运行状态 不
查看>>
Aspose.Words使用教程之在文档中找到并替换文本
查看>>
ORACLE官方文档如何学习
查看>>
google guava包集合类HashMultiset的基本用法
查看>>
linux 进程和线程
查看>>
网工考试!
查看>>
Spring Boot 项目脚本(启动、停止、重启、状态)
查看>>
专访路彦雄:理解语言其实还是很难的
查看>>
Windows 2003服务器群集(负载均衡)简介
查看>>
日程日历示例
查看>>
linux下解决对外udp***
查看>>
fluuter 浮动 和 触摸
查看>>