10 Rules (steps) for replacing the recursive function with stack and while-loop

转自http://www.codeproject.com/Articles/418776/How-to-replace-recursive-functions-using-stack-and

First rule

  1. 定义一个新的数据结构"Snapshot".他的作用是保存队规过程中的中间值数据和状态信息。
  2. "Snapshot" 结构中包含:
    1. 递归函数的参数,但是,如果递归函数的参数是引用类型的参数,则无需放到Snapshot 中. 因此, 实例如下, 参数n 应该放在Snapshot 中,而引用类型的参数 retVal 不放Snapshot 中.

      • void SomeFunc(int n, int &retVal);
    2. 递归的条件分类值 "Stage"  (通常是一个int 值,可以放在 switch语句中,分别处理不同的情况)
      • 细节参照第六条sixth rule.
    3. 存储函数返回值的局部变量
 
// Recursive Function "First rule" example
int SomeFunc(int n, int &retIdx)
{...if(n>0){int test = SomeFunc(n-1, retIdx);test--;...return test; } ... return 0; } 
 
// 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

  1. 在函数顶层创建一个局部变量,用于存储最终结果(递归函数的返回值retVal = currentSnapshot.test)。

    1. 在迭代过程中,它象一个临时变量保存每一次递归调用的返回值.
    2. 如果递归函数返回类型是空void, 忽略此步.
    3. 如果有默认的返回值,用默认值初始化这个局部变量。
 
// Recursive Function "Second rule" example
int SomeFunc(int n, int &retIdx)
{...if(n>0) { int test = SomeFunc(n-1, retIdx); test--; ... return test; } ... return 0; }
 
// 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.// (Second rule返回值retVal = currentSnapshot.test)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 ...  // (Second rule) return retVal; } 

Third rule

  1. 建一个"Snapshot" 类型的堆栈.
 
// Recursive Function "Third rule" example// 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; ... // (Second rule) return retVal; } 

Fourth rule

  1. 创建 "Snapshot" 实例,并初始化输入到迭代中的参数和递归条件分类的初始值"Stage" .
  2. Snapshot实例压栈stack.
 
// Recursive Function "Fourth rule" example// 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); ... // (Second rule) return retVal; } 

Fifth rule

  1. 建一个 while循环,当 堆栈stack 不空时执行循环。
  2. while 循环的每次迭代中, pop 出栈一个 Snapshot 对象;
 
// Recursive Function "Fifth rule" example// 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(); ... } // (Second rule) return retVal; } 

Sixth rule递归条件分类处理

  1. 分2步处理 stages 。第一步对在当前递归函数调用之前的处理,第二步是在当前递归函数调用之后对返回值进行一些运算。
  2. 如果递归过程要调用2个函数, 就要对stages分3步处理:
    1. ** (Stage 1 --> recursive call --> (returned from first recursive call) Stage 2 (recursive call within stage 1)--> (return from second recursive call) Stage 3
  3. 如果有3个不同的递归调用,至少分4步骤处理 stages.
  4. 以此类推.
 
// Recursive Function "Sixth rule" example
int SomeFunc(int n, int &retIdx)
{...if(n>0) { int test = SomeFunc(n-1, retIdx); test--; ... return test; } ... return 0; }
 
// 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: ... // before ( SomeFunc(n-1, retIdx); ) break; case 1: ... // after ( SomeFunc(n-1, retIdx); ) break; } } // (Second rule) return retVal; } 

Seventh rule

  1. 根据不同的Switch 处理不同的Stage
  2. 做相关的处理
 
// Recursive Function "Seventh rule" example
int SomeFunc(int n, int &retIdx)
{... if(n>0) {int test = SomeFunc(n-1, retIdx); test--; ... return test;  } ... return 0; }
 
// 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 ) { ... } ...  break; case 1:  // (Seventh rule) currentSnapshot.test = retVal; currentSnapshot.test--; ... break; } } // (Second rule) return retVal; } 

Eighth rule

  1. 如果递归函数有返回值,在每次循环迭代时,保存返回值到局部变量 (如 retVal ).
  2. 这个局部变量retVal 就是循环结束后,递归的最终值.
 
// Recursive Function "Eighth rule" example
int SomeFunc(int n, int &retIdx)
{...if(n>0) { int test = SomeFunc(n-1, retIdx); test--; ...  return test; } ...  return 0; }
 
// 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 ) { ... } ...  // (Eighth rule) retVal = 0 ; ... break; case 1: // (Seventh rule) currentSnapshot.test = retVal; currentSnapshot.test--; ...  // (Eighth rule) retVal = currentSnapshot.test; ... break; } } // (Second rule) return retVal; } 

Ninth rule

  1. 如果递归含有返回值,把原来递归函数中的关键字 "return" 替换成"while"循环中的关键字 "continue"。
    1. 如果递归函数有返回值,如 "Eighth rule,"所述,把返回值保存到局部变量中 (如 retVal), 然后"continue"继续循环;
    2. 多数情况下, "Ninth rule" 是可选的,但他有助于避免逻辑错误.
 
// Recursive Function "Ninth rule" example
int SomeFunc(int n, int &retIdx)
{...if(n>0) { int test = SomeFunc(n-1, retIdx); test--; ... return test; } ... return 0; }
 
// 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 ) { ... } ... // (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; } 

Tenth rule (and the last...)

  1. 为了实现从递归调用到迭代函数的转换,在每次迭代中,创建一个新的  "Snapshot" 对象, 初始化 这个新的"Snapshot" 对象和递归条件分类 stage, 依据递归函数的参数设置他的成员变量,压栈, 然后继续 "continue"
  2. 如果递归函数调用之后,有其他处理过程,就需要调整当前"currentSnapshot"中保持的递归条件分类 stage ,并把当前"Snapshot"压栈,然后再对新建的"Snapshot"压栈。
 
// Recursive Function "Tenth rule" example
int SomeFunc(int n, int &retIdx)
{...if(n>0) {  int test = SomeFunc(n-1, retIdx); test--; ... return test; } ... return 0; }
 
// 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; } 

Simple Examples by types of recursion

  • Please download RecursiveToLoopSamples.zip
  • Unzip the file.
  • Open the project with Visual Studio.
    • This project has been developed with Visual Studio 2008
  • Sample project contains
    • Linear Recursion Example
    • Binary Recursion Example
    • Tail Recursion Example
    • Mutual Recursion Example
    • Nested Recursion Example

More Practical Example Sources

The below sources contain both a recursive version and a simulated version, where the simulated version has been derived using the above methodology.

  • epQuickSort.h
  • epMergeSort.h
  • epKAryHeap.h
  • epPatriciaTree.h

Why do the sources contain both the simulated version and the recursive version?

If you look at the source, you can easily notice the simulated versions look much more complex than the recursive versions. For those who don't know what the function does, it will be much harder to figure out what the function with the loop actually does. So I prefer to keep both versions, so people can easily test out simple inputs and outputs with the recursive version, and for huge operations, use simulated version to avoid stack overflow.

Conclusion

My belief is that when writing C/C++ or Java code, the recursive functions MUST be used with care to avoid the stack-overflow error. However as you can see from the examples, in many cases, the recursive functions are easy to understand, and easy to write with the downside of "if the recursive function call's depth goes too deep, it leads to stack-overflow error". So conversion from recursive function to simulated function is not for increasing readability nor increasing algorithmic performance, but it is simple way of evading the crashes or undefined behaviors/errors. As I stated above, I prefer to keep both recursive version and simulated version in my code, so I can use the recursive version for readability and maintenance of the code, and the simulated version for running and testing the code.  It will be your choice how to write your code as long as you know the pros and cons for the choice, you are making.

Reference

  • http://www.dreamincode.net/forums/topic/51296-types-of-recursion/
  • EpLibrary 2.0

History

  • 07.02.2015:- Broken link fixed
  • 09.06.2013:- Typo fixed (Thanks to  lovewubo)
  • 08.22.2013:-  Re-distributed under MIT License from GPL v3
  • 08.10.2012: - Table of contents updated
  • 08.04.2012: - Moved the article's subsection to "Howto"
  • 07.23.2012: - Minor fixes on the article
  • 07.13.2012: - Table of contents modified 
    • Sections removed
    • Moved the article to Beginner section
    • Changed the wording
  • 07.13.2012: - Table of contents added.
    • Titles modified.
    • New sections added.
      • Difference between Recursive and Iterative function
      • Pros and Cons of Recursive and Iterative approach
  • 07.12.2012: - Sample bugs fixed.
    • Article re-organized.
    • Ninth and Tenth rule added.
    • Examples for each rule added.
  • 07.11.2012: - Submitted the article.

License

This article, along with any associated source code and files, is licensed under The MIT License

转载于:https://www.cnblogs.com/baiyu/p/4625848.html

如何用堆栈和循环结构代替递归调用--递归转换为非递归的10条军规相关推荐

  1. 数据结构:利用栈,将递归转换为非递归的方法

    利用栈将递归转换为非递归 对于一般的递归过程,仿照递归算法执行过程中递归工作栈的状态变化,可直接写出相应的非递归算法. 步骤 第一次调用的参数push进堆栈,原有递归代码外层加一个while循环,判断 ...

  2. 将递归函数转换为非递归形式

    转自 :https://blog.csdn.net/sunny_ss12/article/details/47095381 1. 递归的调用原理:分而治之 为求一个大规模问题的问题,可以: (1)将原 ...

  3. 10、Java 方法的递归调用详解(递归调用的分析和案例:阶乘、斐波那契、猴子吃桃)

    文章目录 一.递归缩写 二.递归调用 (1) 递归方式求累加和 (2) 递归内存分析 三.递归调用(概念) 四.递归调用举例 五.递归注意事项 六.斐波那契数列 七.猴子吃桃 一.递归缩写

  4. 深度优先搜索(DFS)递归形式改为非递归形式

    DFS将递归改为非递归这个方法的需求来自于一道三维积木组合的题目,还在苦苦调试中,暂且不提. 普通的认识对于递归向非递归的转化无非是使用栈,但是结合到深度搜索如何将栈很好利用,如何很好保存现场,都不是 ...

  5. 利用栈将递归转换为非递归的方法

    通过上述讨论,可以看出递归程序在执行时需要系统提供隐式栈这种数据结构来实现,对于 一般的递归过程,仿照递归算法执行过程中递归工作栈的状态变化可直接写出相应的非递归算法. 这种利用栈消除递归过程的步骤如 ...

  6. 二叉树前序遍历(递归法和迭代法(即非递归法))——C++

    声明:本文原题主要来自力扣力扣,记录此博客主要是为自己学习总结,不做任何商业等活动 本文主要讲解二叉树的前序遍历递归法和迭代法.中序遍历和后序遍历可以参考博主下面两篇博客:二叉树中序遍历(递归法和迭代 ...

  7. 数据结构——二叉树的递归遍历算法与非递归遍历算法+层次遍历算法

    (文章篇幅有点长,二叉树的递归遍历算法不作详细分析,但是二叉树的非递归遍历算法和层次遍历算法都有非常详细的分析过程,记得往下翻哦!) 二叉树的递归遍历算法实现 我们首先用递归的方法先序遍历创建这样一棵 ...

  8. 分治法 —— 快速排序(递归,迭代,非递归)

    快速排序 快速排序三种方法,你值得参考!!! 快速排序 1,快速排序之递归 Code 递归 2,快速排序之迭代 Code 迭代 3,快速排序之非递归 Code 非递归 4,结语 快速排序在所有排序中基 ...

  9. 快排 递归三种方式+非递归 --排序

    方法1–Hoare #include"Sort.h" void PrintArray(int* a, int n) {for (int i = 0; i < n; ++i){ ...

最新文章

  1. webform里的验证控件
  2. Idea--使用Idea调试设置
  3. Google App Engine给我们带来了什么?
  4. if null 锁 java_史上最全 Java 中各种锁的介绍
  5. [欧拉路]CF1152E Neko and Flashback
  6. PLSQL_day01
  7. P3172-[CQOI2015]选数【dp,容斥】
  8. 吴恩达神经网络1-2-2_图神经网络进行药物发现-第1部分
  9. struts2 - ation 访问 Servlet api
  10. 出错也很美的404页面设计模板
  11. 二叉树的三种非递归遍历
  12. Java自学路线图之Java进阶自学
  13. 2D激光雷达的多传感器拼接
  14. 黑苹果 hackintosh 声卡驱动
  15. (二三)计算机组成原理笔记整理之系统总线(总线判优方式,标准传输率,数据总线,地址总线与MDR,MAR的关系等)
  16. 【面试】2019工商银行软件开发中心(上海)笔面试记录
  17. 苹果手机咋用计算机,苹果手机怎么通过usb连接电脑上网
  18. 如何在 Python 中使用 Plotly 创建太阳系的 3D 模型 (教程含源码)
  19. 《胡雪岩·红顶商人》—— 读后总结
  20. 把汉字转换成拼音的util

热门文章

  1. Windows 路由追踪tracert命令使用示例
  2. Docker私有仓库搭建与配置
  3. python录入学生信息网_干货满满 | Python趣味编程教学实践
  4. 同一套代码,复制出来,变成另外一个项目
  5. Python 内建函数 max/min的高级用法
  6. Undefined function or method ' ' for input arguments of type 'double' ---错误解决办法
  7. 小型项目服务器要多少,小型服务器需要什么配置
  8. oss多线程 上传_解读阿里云oss-android/ios-sdk 断点续传(多线程)
  9. STM32 之十六 深入了解 ADC 工作原理及参考电压变动的影响
  10. 设计模式理解:装饰模式Decorator