关于递归函数转换非递归函数的一些方式
前言
最近在重拾算法和数据结构的一些知识,打算从基本的树的遍历算法入手。网上翻看了很多的二叉树的遍历算法相关文章,二叉树的遍历有前、中、后三种遍历方法。最简单的用递归方法遍历,三种方法的逻辑一目了然很好理解,看到非递归遍历方法时,前序遍历还能理解,中序和后序遍历看的理解起来感觉不那么顺了,所以想先研究一下递归方法改非递归方法的一些方法,翻看了一些文章结合自己的理解记录下对递归方法改成非递归方法的一些方法。
目的
既然是要将递归方法转换成非递归方法,那首先就要明白为什么要将递归方法转换成非递归方法,也就是递归转非递归的目的和意义何在?如果这样的转换没有任何实际意义那也就不存在转换的必要了。下面收集了一些递归和非递归方法的一些优缺点,自己权衡:
- 递归函数逻辑清楚,以数学化的方式来写函数,便于理解。非递归函数一般相对逻辑性和可理解性要差些。
- 大部分语言的编译器对递归的层数有限制。非递归函数没有这个限制。当然有时间和性能上的要求。
- 递归方式使用了系统的栈来存储函数的参数和变量等,造成额外的更多的开销。 非递归方式要分情况考虑系统开销,后面例子测试会有比较。
可行性
既然清楚了递归函数转换成非递归函数的目的,下面就要提出一个问题,那就是是否所有的递归函数都能转换成非递归函数即转换的可行性。这个答案是肯定的。一个显然的原因是:我们计算机是如何运行递归函数的?学习过汇编语言的童鞋可以很自然的理解这个原因。汇编语言中对函数的调用通过call指令来执行, call指令通过将调用程序段执行的寄存器及代码执行计数器入栈的方式来执行被调用函数,被调用函数执行完毕后通过return指令来出栈。所以原则上我们可以借助栈这个结构用程序来模拟函数调用过程,也就是可以实现非递归转换成递归的方法。
转换的几种途径
递归转换成非递归一般有以下几种途径:
- 可行性里面介绍了借助栈来实现转换。
- 使用循环和数组方式来转换。
这两种方法效率不同,且循环数组方式不一定能解决所有的转换问题,查阅网上的一些资料可以参考:
- 公众号:Linux云计算网络的 : 漫谈递归转非递归.
- 奔跑de五花肉的: 递归算法转换为非递归算法的技巧.
转换示例
第一个例子:阶乘n!
由易入难首先选择阶乘,n的阶乘计算方法: n! = n * (n-1) * (n-2) * … * 3 * 2 * 1 (n>0且n属于自然数),它也可以是一个最简单的递归函数,假设f(n)=n!,则f(n)=n * f(n-1),f(1) = 1,那么写成递归代码如下(本文代码均用Python描述):
# 递归求阶乘 n>0
def recu_fact(n):
if n==1:
return 1
else:
return n * recu_fact(n-1)
这个递归函数就是尾递归函数,我们可以很自然的使用循环来改写成非递归结构,代码如下:
# 非递归方式求阶乘,循环数组方法改写
def cycle_fact(n):
result = 1
for i in range(1, n+1):
result = result * i
return result
然后,我们用栈模拟方式来改写成非递归结构,python中的list有append()和pop()方法实际上可以看成栈,但是为了便于理解,我们先定义一个文件stack.py来模拟一个栈类,代码如下:
#!/usr/bin/python
# coding:utf-8
# stack.py
# 使用列表封装的一个简易Stack类,便于演示算法
class Stack(object):
def __init__(self):
self._list = []
# 压栈
def push(self, node):
self._list.append(node)
# 出栈
def pop(self):
return self._list.pop()
# 栈是否为空
def empty(self):
return len(self._list) == 0
# 栈顶元素
def top(self):
return self._list[-1]
def __len__(self):
return len(self._list)
push(), pop(),empty(),top()是栈常用的几个方法,不多解释。
然后尝试用栈模拟来变更成非递归方法,代码如下:
# 非递归阶乘计算
# n>0; n=1:f(1)=1, n>1:f(n)=n*f(n-1)
# 理解last和top指针的作用,cmp(last,top)决定是压栈还是出栈
def nonrecu_fact

最低0.47元/天 解锁文章
2207

被折叠的 条评论
为什么被折叠?



