递归可以转成循环、尾递归。
其中循环一种是用栈帮助完成的,这种方式一定能把递归转为循环,因为递归本身就是用栈完成的。
另一种循环我认为其实是同一个问题的另一种解决办法,它跟递归之间并没有什么映射关系。
尾递归也是一样,不一定会能转换成尾递归。
用栈结构把递归转换为循环
就像前文讲的,二叉树的中序遍历,需要一个栈记录下来接下来要处理右子树的节点。
中序遍历如果用公式表示:
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的栈顶、函数返回地址保存在栈中。
这些数据随着一遍遍的调用,不能被释放,逐渐变多。
而如果用循环的话,栈顶、函数返回地址就不需要了,局部变量也可以共用一份,只有被传进来的参数这一个重要数据被放在栈中,冗余数据就没有了。