你还不理解递归,我真的受不了你了

https://juejin.im/post/5e648847e51d4526e32c3d9ejavascript


原本也没什么计划写这篇文章。可是答应了一位同窗为ta解答下面那张图的故事,既然答应了那就写写吧php

来先上图: java

好了,给你们讲故事的时间到了,让我想一想该怎么说...面试

这是一个关于递归的故事

看图说故事start编程

首先,我给这图的男主角起名为小明,女主角叫小红浏览器

小明跟小红是大学同窗并且是同桌。他们一块儿去上课,一块儿去吃饭,这样的日子持续了两年,他们终于在一块儿了。并且在一个夜黑风高的晚上他们一块儿去看电影还kiss上了(tmd,小明真不是人,那么好色...),以后的日子他们一块儿过情人节,一块儿去游乐园坐过山车,一块儿旅行滑雪,一块儿去动物园看大象,一块儿行街,终于有一天小明向小红求婚了。结婚以后他们养了一条狗,还常常一块儿看电视,一块儿吃饭,一块儿工做,一块儿睡觉,过得小日子太幸福了,这样的日子一天一天的过去,他们都老了,忽然有一天小红去世了,以后的日子,小明一我的吃饭,工做,睡觉...由于太想小红,小明慢慢的得了忧郁症,幻想症。幻想能不能有一种办法让小红回来,让他们回到刚刚在一块儿的时候,某一天以后,小明开始翻书,上网差资料,作实验....终于有一天他的实验成功了,他研究出了一种生化药水。以后小明去找了一个算命先生挑了一天良辰吉日,拿着鲜花和药水去了小红的墓地,他把药水掉下小红的墓地上,过了一段时间,药水发效了,小红变成了一个生化在墓地爬了出来,咬了小明一口,小明也变成生化,以后他们又回到了在人世间刚刚在一块儿的日子了;虽然画图的做者画少了两张图(kiss和情人节),但本渣想确定是做者太困了,忘记了,因此,咱们本身脑补一下,哈哈哈。(这里咱们假设生化的世界也有生死轮回,上图做者也没画完他们在生化界,完整的过完一篇他们在人世间的一世,因此,咱们要继续脑部,在个人故事里,小红最后仍是死了,小明继续研究药水,可是此次再也不成功,不能再成功也是个人故事里必须,等下我会告诉你为何必须不能再成功的缘由。故事的最后小明也死去....)编程语言

"MD,为了让大家搞懂递归还要给大家讲个故事,我也是绞尽脑筋啊,我是否是疯了!"函数

递归

故事说完了,咱们回到正题,说说咱们今天真正的主角---递归post

递归不论是在java,php,go,javascript仍是Node等编程语言里都会有,按本渣的理解,本渣以为递归就是一种思想。spa

那么递归是什么呢?

递归就是一个函数在知足必定的条件的时候,调用自身的一个过程。

递归不但要有开始条件,还要有结束条件,否则的话,就会形成死循环,从而消耗尽浏览器的内存,致使浏览器蹦掉。(这也是我刚刚在故事里讲的小明在生化界再次研究药水为何必定要失败的缘由)

例子1

咱们先来作个简单的例子:从1-100相加

首先咱们先来肯定它的开始条件和结束条件。由于将1-100累加的话,它是从后面递归回来的,而且咱们是从1始相加,因此,咱们结束条件就肯定了,就是到递归到 1 的时候;那么开始条件就是从 2 开始,每次调用自身的时候都要减 1 ,否则就会出现死循环。

既然条件已经肯定,那咱们如今就用代码来实现一下。

function sum(n) {

    if (n === 1) return 1

    return sum(n-1) + n

}

sum(100)
复制代码

好了,就两行代码,就实现了1-100的累加,看起来很是简洁;若是用for实现的话,那么逻辑就多一些。

function sum2(n) {

    let count = 0
    
     for (let i = 0; i <= 100; i++) {
     
     count += i
     
    }
    
     return count
     
}

 sum2(100)
复制代码

可是咱们,也要看状况来定是用递归来实现本身的需求,仍是用for来实现本身的需求;本渣我的建议尽可能少用递归;由于递归的执行速度要比for的执行速度慢。

本渣来带你作个验证

验证用for执行一次1-10000的累加须要多长时间

function timer(){
    let d = new Date()
    console.log(d.getTime())
    sum2(10000)
    let d2 = new Date()
    console.log(d2.getTime())
}
timer()
复制代码

结果

你多刷新几回浏览器,你会发现,执行1-10000累加,须要的时间是在0-1毫秒之间徘徊。

验证用递归执行一次1-10000的累加须要多长时间

function timer(){
    let d = new Date()
    console.log(d.getTime())
    sum(10000)
    let d2 = new Date()
    console.log(d2.getTime())
}
timer()
复制代码

结果

递归执行1-10000累加须要的时长在1-3毫秒之间徘徊,明显for是比较快的,这仍是10000次,要是10W,100W呢,那就更加明显来,并且递归不能保证不让浏览器崩掉(本渣尝试在谷歌执行了1-10W的累加,结果浏览器的调用栈溢出,有兴趣能够本身试试)。因此本渣是建议少用递归的。

可是建议少用,有时也不能不用、好比有些公司会出一道这样的面试题:请用setTimeout模拟setInterval

那么用setTimeout模拟setInterval,用for确定是实现不了的,那就不得不用递归来实现了。

下面咱们用代码简单的实现一下上面那个面试题

function timer(ms){
        setTimeout(()=>{
            console.log('来了小老弟')
            timer(ms)
        },ms)
    }
    
复制代码

好了,一个简版的setTimeout模拟setInterval就实现了。

“md,写着写着,全身发热”

“我是否是得了xx肺炎”

”哦,原来不是,原来是想去蹲坑🤣“

好了,最后举一个面试中常常用到的递归面试题:实现一个对象的深拷贝

实现深拷贝,咱们也不能一直for下去,由于咱们都不知一个对象会有多少层,因此选择了递归来实现。

咱们先来分析一个下它们的开始条件和结束条件。

深拷贝就是遇到引用值的时候,好比([],{}),继续遍历它们,将它们值复制到另外一个地方,而不是复制它们的引用过去。那么深拷贝的开始条件就是遇到类型为object的值,结束的条件就是没有再遇到object类型的值。

如今咱们来用代码实现一下,上面的神拷贝。

function deepClone(p,c){
    
        for(let props in p){
        
            if(typeof p[props] ==='object'){
            
                c[props] = p[props] instanceof Array ? [] : []
                
                deepClone(p[props],c[props])
                
            }else{
            
                c[props] = p[props]
                
            }
        }
        
    }
复制代码

好了,深拷贝也就实现了。

总结

最后咱们来总结一下递归

什么时候使用递归

  • 子问题为同类事物
  • 必需要有开始条件和结束条件

优势

  • 简洁
  • 符合思惟习惯,容易理解

缺点

  • 效率低(上面也带你们验证了)
  • 递归层次太深的话,消耗内存,且容易栈益出(上面也说了)
  • 递归中不少数据都是重复的,影响执行效率

看完若是你还不理解递归,还不知到何时用它,还不知它的优缺点,我之后都不给大家讲故事了。

相关文章
相关标签/搜索