javascript 递归和尾递归
递归 (recursions)
1.定义
Wiki [1]:Recursion is the process of repeating items in a self-similar way.
具体到计算机中去 [2]: 递归(英语:Recursion),又译为递回,在数学与计算机科学中,是指在函数的定义中使用函数自身的方法。
英文的Recursion从词源上分析只是”re- (again)” + “curs- (come, happen)” 也就是重复发生,再次重现的意思。 而对应的中文翻译 ”递归“ 却表达了两个意思:”递“+”归“。 这两个意思,正是递归思想的精华所在。从这层次上来看,中文翻译反而更达意。
最简单的示例,求解阶乘:
整个算法的思路是:
- 将a柱子上的n-1个盘子暂时移到b柱子上
- a柱子只剩下最大的盘子,把它移到目标柱子c上
- 最后再将b柱子上的n-1个盘子移到目标柱子c上
问题的缩小策略是我们怎么把n个盘子从a移到c,同样适用于n-1个盘子从a移到c。
当n一直缩小到3时,我们先把最上面的两个盘子移动到b,然后把最大的盘子移动到c,然后再把b上的两个盘子移动到c。 当n一直缩小到2时,问题终于变得直观了,我们将最上面的盘子移到b,然后把最下面的盘子移到c,最后再把b的盘子移到c。 当n缩小到1时,我们直接将a最上面的盘子移到c就OK了。
这就是问题的终止条件。
尾递归(tail recursion)
递归非常耗费内存,因为需要同时保存成千上百个调用记录,很容易发生”栈溢出”错误(stack overflow)。但对于尾递归来说,由于只存在一个调用记录,所以永远不会发生”栈溢出”错误。
尾调用,是指函数内部的最后一个动作是函数调用。该调用的返回值,直接返回给函数。
普通递归的复杂度为O(n), 尾递归的复杂度 O(1)。由此可见,”尾调用优化”对递归操作意义重大。
总结
总结一下,递归本质上是一种循环操作。纯粹的函数式编程语言没有循环操作命令,所有的循环都用递归实现,这就是为什么尾递归对这些语言极其重要。对于其他支持”尾调用优化”的语言(比如Lua,ES6),只需要知道循环可以用递归代替,而一旦使用递归,就最好使用尾递归。
实践
实际项目中遇到的就是AJAX请求回来的数据data,要递归出一棵树。具体代码见Gist