自我测试

本篇文章的测试用例及调试方法见前言

说明

递归是一种解决问题的方法,他从解决问题的各个小部分开始,直到解决最初的大问题.递归通常涉及函数调用自身.

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等时的值)

posted on 2020-11-22 18:37  人生之外的路途  阅读(157)  评论(0编辑  收藏  举报