定义
程序自己调用自己的方法就叫 递归。
递归,在我的理解当中,就是 关系还没决裂之前,有来有往 。
- 关系决裂,就是断掉递归的条件,如下面阶乘递归实现中的 if 语句
- 有来,也就是有递进的关系,有一个规律的关系层层递进,如下面阶乘递归实现中的
n*f(n-1) - 有往,就是 return ,只有 return 回去才能让结果完整。
这三个条件缺一不可。
阶乘递归
function factorial(n) {if (n <= 1) return n;return n * factorial(n - 1);}console.log(factorial(5));
斐波那契数列
它是一个由 0、1、1、2、3、5、8、13、21、 34 等数组成的序列。数 2 由 1 + 1 得到,数 3 由 1 + 2 得到,数 5 由 2 + 3 得到,以此类推。斐波那契数列的定义如下:
- 位置0的数值是0
- 位置1和2的数值都是1
- n的值都是 (n-1) + (n-2) 的和
function fibonacci(n) {return n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);}console.log(fibonacci(5))
这里需要特别注意的是,这个函数求的是第n个斐波那契数是什么,而不是n之前的所有斐波那契数的和
普通循环优化
递归的性能其实并不好,特别是在计算斐波那契数时,如果你上面的函数n输入为100,页面将会直接卡死,所以,递归的实现看起来是比较优雅,但在大计算量的时候,还得慎重,这里提供一个普通循环实现的斐波那契数列实现。
function fibonacciIterative(n) {if (n < 1) return 0;if (n <= 2) return 1;let fibNMinus2 = 0;let fibNMinus1 = 1;let fibN = n;for (let i = 2; i <= n; i++) { // n >= 2fibN = fibNMinus1 + fibNMinus2; // f(n-1) + f(n-2)fibNMinus2 = fibNMinus1;fibNMinus1 = fibN;}return fibN;}
输出n位斐波那契数
function printFibonacci(n) {if (n <= 0) return;let result = [];for (i = 0; i < n; i++) {if (i <= 1) {result.push(i);} else {result.push(result[i - 1] + result[i - 2]);}}return result;}console.log(printFibonacci(5))
尾递归
尾调用
讲尾递归之前,需要先了解 尾调用 。
尾调用,是指一个函数最后的返回是一个函数的调用。如:
function f(x){return g(x);}// 不是尾调用function f(x){g(x);}// 不是尾调用function f(x){return g(x) + 1;}
如上代码,调用完 g(x) 之后没有返回,或者还有其他运算,都不算尾调用。
那么,在递归中的尾调用,则就是尾递归。
尾递归阶乘
function factorial(n, total = 1) {if (n <= 1) return total;return factorial(n - 1, n * total);}console.log(factorial(3));
尾递归斐波那契数列
'use strict'function fibonacci(n, current = 0, next = 1) {if (n === 1) return next;if (n === 0) return 0;return fibonacci(n - 1, next, current + next);}console.log(fibonacci(100))
如何将递归改写为尾递归
尾递归的核心就是:最后返回的直接是函数的调用。所以,把原本在最后调用递归后还有的运算,通过传参的方式进行传递,从而优化掉递归后的运算,从而实现尾递归。
一般从一开始就写出完美的尾递归比较有难度,可以先按正常实现递归,在慢慢优化。
参考
- 《尾调用优化》:https://www.ruanyifeng.com/blog/2015/04/tail-call.html
- 《学习JavaScript数据结构与算法(第3版)》
