算法--递归转非递归

1,109 阅读2分钟

递归可以转成循环、尾递归。

其中循环一种是用栈帮助完成的,这种方式一定能把递归转为循环,因为递归本身就是用栈完成的。

另一种循环我认为其实是同一个问题的另一种解决办法,它跟递归之间并没有什么映射关系。

尾递归也是一样,不一定会能转换成尾递归。

用栈结构把递归转换为循环

就像前文讲的,二叉树的中序遍历,需要一个栈记录下来接下来要处理右子树的节点。

中序遍历如果用公式表示:

f(node) = f(left) + g(value) + f(right)

f函数会递归遍历节点的子树,g函数只是访问了value,是 O(1) 级别的访问

这跟我们常见的公式 f(n) = f(n-1) + f(n-2) 非常类似。所以这种情况也可以通过栈解决。

我的经验是:如果类似于 f(n) = f(n-1) + n 公式,把一个规模的计算转换成一个更小规模的计算,一般是能转换成循环的,而一个规模的计算转换成多个更小规模的计算时,一般是通过栈解决的。

为什么同样是通过栈,递归就容易栈溢出

这两个栈是两个概念,栈溢出的栈是说内存区域栈,与之对应的是堆、常量区等,系统对栈的大小有限制,比如 iOS 系统,子线程的栈只有512K,用完就溢出了。而递归所用的就是这块区域的内存。

通过栈实现循环的栈是指数据结构栈,这个栈是放在堆区域的,堆区域很大的,一般情况下,不太容易用完。

而且,递归的过程中,是有非常多冗余数据的。

假如把函数A第一次被调用称作a1,把函数A被通过递归第二次调用称作a2,那么:

a1的栈空间有被传进来的参数、局部变量。

在调用a2时,要把a1的栈顶、函数返回地址保存在栈中。

这些数据随着一遍遍的调用,不能被释放,逐渐变多。

而如果用循环的话,栈顶、函数返回地址就不需要了,局部变量也可以共用一份,只有被传进来的参数这一个重要数据被放在栈中,冗余数据就没有了。