自我测试
本篇文章的测试用例及调试方法见前言
说明
递归是一种解决问题的方法,他从解决问题的各个小部分开始,直到解决最初的大问题.递归通常涉及函数调用自身.
const test = (i) => {
console.log(i)
test(++i);
}
这是一种明显的递归,自己调用自己
function test2(i){
console.log("test2: " + i++)
test(i)
}
function test(i){
console.log("test: " + i++)
test2(i)
}
这个也是一种递归,通过两个方法之间的相互调用,然后实现递归.但是这种代码我们一般是不会直接拿去运行的.为什么呢???
相信你也看出来了,上面的递归写法有问题,我调用我自己,然后自己调用自己,然后自己又调用了自己......,形成了一个闭环,所以递归的最重要的一个点出现了 基线条件(跳出循环的条件)
调用栈(这个对后面分析递归很重要)
每当一个函数被一个算法调用时,该函数会进入调用栈的顶部.当使用递归的时候,每个函数调用都会堆叠在调用栈的顶部,这是因为每个调用都可能依赖前一个调用的结果
尾部调用优化(ES2015新知识点)
案例
let i = 0;
function recursiveFn(){
i++;
recursiveFn();
}
try{
recursiveFn();
}catch(ex){
console.log(ex)
}
如果函数内的最后一个操作是调用函数(如案例),会通过"跳转指令"(jump)而不是"子程序调用"(subroutine call)来控制.也就是说,在ES2015中,这里的代码可以一直执行下去.因此,具有停止递归的基线条件非常重要尾调用优化更多信息
练习
阶乘
说明:
5的阶乘: 5 * 4 * 3 * 2 * 1
9的阶乘: 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1
代码
function recursionFactorial(num: number): number {
//找到结束条件
if (num === 1) {
return 1;
}
return num * recursionFactorial(num - 1);
}
还是一定要找到 基线条件,这个是递归的必要条件,不然你的程序会死循环,然后卡死
图解
是否还喜欢这种图解,如果喜欢,可以进入下一篇章第十章 树,带你走进树的遍历
斐波那契数列
说明
0 1 1 2 3 5 8 13 21 34 ....
第1个数: 0
第2个数: 1
第3个数 = 第2个数 + 第1个数
第4个数 = 第2个数 + 第3个数
第5个数 = 第4个数 + 第3个数
........
图解
代码
/*求n个斐波那契数的和*/
function newRecursionSequence(n: number): number {
if (n === 0) return 0;
if (n <= 2) return 1
return newRecursionSequence(n - 1) + newRecursionSequence(n - 2);
}
优化版代码
function newNewRecursionSequence(n: number) {
let memo: Array<number> = [0, 1];
let fbnc = (n: number): number => {
if (memo[n] != null) return memo[n];
//新算出的值保存在memo数组里
return memo[n] = fbnc(n - 1) + fbnc(n - 2);
}
return fbnc(n);
}
优化版对比第一版好在对数据进行了缓存,这样就不用反复的计算数据(图解中可以看到我们反复计算了n为 3 ,2等时的值)