逐步学习什么是递归?通过使用场景来深入认识递归。

递归算法

我们先来看一下定义。递归算法,是将问题转化为规模缩小的同类问题的子问题,每一个子问题都用一个同样的算法去解决。一般来说,一个递归算法就是函数调用自身去解决它的子问题。

递归算法的特点:

在函数过程中调用自身。 在递归过程中,必须有一个明确的条件判断递归的结束,既递归出口。 递归算法简洁但效率低,通常不作为推荐算法。

上面这些是百度百科的解释,讲的也是十分明确,大家配合实例来细细琢磨。

求一个数的n次方(n>=0)

我们拿到问题的时候,我们按照定义的说明,可以先将规模缩小到同类的子问题。比如:求一个数的n次方,就是把n个相同的数相乘的积。计算过程就是第一个数乘以第二个数,再把这两个数的乘积跟第三个数相乘,依次把上一次的乘积跟下一个数相乘,直到乘到第n个数。规律就是把这一次的乘积和下一个数相乘。递归的目的就是把一个大问题拆分成若干个用同样逻辑解决的小问题。这里我们用递归的解法就是:

//非尾递归
function com2 (num, n) {
    if(n<=0){
        return 0;
    }else if(n==1){
        return num;
    }
    return num*arguments.callee(num, n-1)
}
// 用法:
// com2(12,2)
// > 输出144
复制代码

我们可以看到函数com2第一个参数是num(数值),第二个参数是n(数值的n次方)。函数体内部判断如果n<=0,则返回0;如果n==1则返回数值num;这里这两个条件就是递归函数的递归出口。n<=0是为了处理求一个数的<=0次方时的异常处理。n==1是为了退出递归循环。

最后面的return num*arguments.callee(num, n-1)就是我们的主递归逻辑了。把一个数乘以下一个数,直到n==1时退出递归循环,并且把最后的乘积一层层返回出来。

//尾递归
function com2 (num, n) {
    if(n<=0){
        return arguments[2] || 0;
    }
    var product = num;
    if(arguments[2]){
        product = num*arguments[2];
    }
    return arguments.callee(num, n-1, product)
}
// 用法:
// com2(12,2)
// > 输出144
复制代码

尾递归。为什么我要说尾递归,上面我们也说了,递归算法简洁但效率低,那么有没有优化方案呢?答案是“有”。那就是尾递归。

什么是尾递归?
尾递归的判断标准是函数运行【最后一步】是否只调用自身。通常会把上一个方法的返回值当作参数传给下一个递归方法。
如上面代码:return num*arguments.callee(num, n-1)就不是尾递归,因为最后执行的是num*函数自身调用,而不是纯粹的调用函数自身。return arguments.callee(num, n-1, product)就是纯粹的调用函数自身的尾递归。

那么为什么递归效率低呢?

众所周知,递归非常消耗内存,因为需要同时保存很多的调用帧,这样,就很容易发生“栈溢出”。

尾递归的作用就是保留一个调用记录。这样速度就可以提升上来了。

遗憾的是,当前提供尾递归优化的浏览器比较少。

实际应用场景

将数组obj格式:

var obj = [
    {id:1, parent: null},
    {id:2, parent: 1},
    {id:3, parent: 2},
];
复制代码

转换为obj2格式:

var obj2 = {
    obj: {
        id: 1,
        parent: null,
        child: {
            id: 2,
            parent: 1,
            child: {
                id: 3,
                parent: 2
            }
        }
    }
}
复制代码
代码实现:
var obj2 = {};
function createObj2(obj, child){
    if(child.parent){
        if(obj.obj){
            createObj2(obj.obj, child);
        }else{
            if(obj.id === child.parent){
                obj.child = {
                    id: child.id,
                    parent: child.parent,
                }
            }else{
                if(obj.child){
                    createObj2(obj.child, child);
                }else{
                    console.log('obj2未匹配到对应的parent对应关系')
                }
            }
        }
    }else{
        obj.obj = {
            id: child.id,
            parent: child.parent,
            child: {}
        }
    }
}
obj.forEach((item, item_i) => {
    createObj2(obj2, item)
})
console.log('obj2:', obj2)
复制代码

结语

一般树状结构的都可以使用递归查询,比如:
查询地区,树状的菜单等等。

注意:递归比普通的算法耗内存,深度递归的函数可能会因为返回堆栈溢出而运行失败。在适当的时候用递归,不要滥用递归。

备注:喜欢这篇文章的话,可以关注我,我会持续更新技术文章到我的掘金主页,喜欢交流技术的朋友可以加我的专业前端QQ交流群。
QQ群号:583575104 或 366420656
    原文作者:算法小白
    原文地址: https://juejin.im/post/5a94d6476fb9a06348538871
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞