摘要:我们经常会用到递归函数,但是如果递归深度太大时,往往导致栈溢出。而递归深度往往不太容易把握,所以比较安全一点的做法就是:用循环代替递归。文章最后的原文里面讲了如何用10步实现这个过程,相当精彩。本文翻译了这篇文章,并加了自己的一点注释和理解。

 

目录

  1.  简介
  2. 模拟函数的目的
  3. 递归和模拟函数的优缺点
  4. 用栈和循环代替递归的10个步骤
  5. 替代过程的几个简单例子
  6. 更多的例子
  7. 结论
  8. 参考
  9. 协议

1 简介

  一般我们在进行排序(比如归并排序)或者树操作时会用到递归函数。但是如果递归深度达到一定程度以后,就会出现意想不到的结果比如堆栈溢出。虽然有很多有经验的开发者都知道了如何用循环函数或者栈加while循环来代替递归函数,以防止栈溢出,但我还是想分享一下这些方法,这或许会对初学者有很大的帮助。

2 模拟函数的目的

  如果你正在使用递归函数,并且没有控制递归调用,而栈资源又比较有限,调用层次过深的时候就可能导致栈溢出/堆冲突模拟函数的目的就是在堆中开辟区域来模拟栈的行为,这样你就能控制内存分配和流处理,从而避免栈溢出。如果能用循环函数来代替效果会更好,这是一个比较需要时间和经验来处理的事情,出于这些原因,这篇文章为初学者提供了一个简单的参考,怎样使用循环函数来替代递归函数,以防止栈溢出?

3 递归函数和模拟函数的优缺点

  递归函数:

  优点:算法比较直观。可以参考文章后面的例子

  缺点:可能导致栈溢出或者堆冲突

  你可以试试执行下面两个函数(后面的一个例子),IsEvenNumber(递归实现)IsEvenNumber(模拟实现),他们在头文件"MutualRecursion.h"中声明。你可以将传入参数设定为10000,像下面这样:

#include "MutualRecursion.h" 

bool result = IsEvenNumberLoop(10000); // 成功返回

bool result2 = IsEvenNumber(10000);     // 会发生堆栈溢出

 有些人可能会问:如果我增加栈的容量不就可以避免栈溢出吗?好吧,这只是暂时的解决问题的办法,如果调用层次越来越深,很有可能会再次发生溢出。

   模拟函数:

  优点:能避免栈溢出或者堆冲突错误,能对过程和内存进行更好的控制

  缺点:算法不是太直观,代码难以维护

 

4 用栈和循环代替递归的10个步骤

第一步

定义一个新的结构体Snapshot,用于保存递归结构中的一些数据和状态信息

Snapshot内部需要包含的变量有以下几种:

  A 一般当递归函数调用自身时,函数参数会发生变化。所以你需要包含变化的参数,引用除外比如下面的例子中,参数n应该包含在结构体中,而retVal不需要。

void SomeFunc(int n, int &retVal);

  B 阶段性变量"stage"(通常是一个用来转换到另一个处理分支的整形变量),详见第六条规则

  C 函数调用返回以后还需要继续使用的局部变量(一般在二分递归和嵌套递归中很常见)

代码:

 1 // Recursive Function "First rule" example
 2 int SomeFunc(int n, int &retIdx)
 3 {
 4    ...
 5    if(n>0)
 6    {
 7       int test = SomeFunc(n-1, retIdx);
 8       test--;
 9       ...
10       return test;
11    }
12    ...
13    return 0;
14 }
15 
16 
17 // Conversion to Iterative Function
18 int SomeFuncLoop(int n, int &retIdx)
19 {
20     // (First rule)
21     struct SnapShotStruct {
22        int n;        // - parameter input
23        int test;     // - local variable that will be used 
24                      //     after returning from the function call
25                      // - retIdx can be ignored since it is a reference.
26        int stage;    // - Since there is process needed to be done 
27                      //     after recursive call. (Sixth rule)
28     };
29     ...
30 }
View Code

相关文章: