Skip to content

递归

递归是当在一定条件下时,循环调用自身的算法,因此问题也分成基线条件和递归条件

🤔🤔🤔 案例

比如有一个箱子,打开之后有多个盒子,盒子里面又有盒子。而金币就在某一个盒子中。如果遇到这种情景,你会使用什么方法???

第一种方法

使用循环,从盒子中取出一个盒子,在里面找,如果还是盒子,则放到盒子堆里面,一遍下次循环查找;如果是金币则成功!

第二种方法

函数调用自己

javascript
// 伪代码
function find (box) {
    box.forEach(item => {
        if(item.isBox()) {
            find(box)
        } else if(item.isGold()) {
            console.log('我找到金币啦')
        }
    })
}

TIP

“如果使用循环,程序的性能可能更高;如果使用递归,程序可能更容易理解。如何选择要看什么对你来说更重要。”

WARNING

注意:递归函数调用自己,因此很容易会导致无限循环

基线条件:函数不再调用自己,即跳出循环的条件。如案例的 item.isGold()

递归条件:函数调用自己的条件。如案例的 item.isBox()

基本递归就讲完了,其实递归算法还是比较简单的,最重要的是分清两种条件,而且基线条件必须是在某一个点上会发生的,不能是一个永远不会触发的条件。

🖥️ 而递归在计算机的运行机制——调用栈

调用栈

前置知识 👉🏻 《栈和队列》

举例

javascript
function greet(name) {
    console.log('去哪呀?', name)
}

function sayBye() {
    console.log('我有事,我先走啦')
}

function sayHi(name) {
    console.log('hi, ', name)
    greet(name)
	sayBye()
}

sayHi('至安')

// 首先压入 sayHi,打印 “hi, 至安”;
// 然后执行到greet(name),压入greet('至安'),打印 “去哪呀?至安”,执行完毕弹出 greet。
// 然后执行到sayBye,压入 sayBye,打印 “我有事,我先走啦”,执行完毕弹出 sayBye。
// 然后继续执行 sayHi,执行完毕,弹出 sayHi

递归的调用栈就是压入和弹出栈的过程,每触发一次递归条件则压入栈的过程,而触发基线条件则就是弹出栈的过程

因此也说明了如果没有基线条件,一直触发递归条件则一直入栈,栈最终会爆掉,程序也挂了。

TIP

其实刚开始的找金币的两种方法,原理上都是一样的,第一种方法的盒子堆就相当于第二种方法的栈,只是一种我们来跟踪,一种栈帮我们做了。

注意:使用栈虽然很方便,但是也要付出代价:存储详尽的信息可能占用大量的内存。每个函数调用都要占用一定的内存,如果栈很高,就意味着计算机存储了大量函数调用的信息。因此需要自己去衡量使用。

Released under the MIT License.