炒冷饭系列5:了解一下Javascript中的递归?

1,016 阅读9分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情

前言

四月的更文挑战活动真是让人太卷了,恰好又是临近五一放假赶上公司最忙的时刻,每天都是忙得飞起,更别说写文章的时间了。但是老话说:时间就像海绵,挤一挤还是有的。这不,紧赶慢赶还是陆陆续续写了6篇文章了,感兴趣的可以滑到文章底部查看往期精彩文章

今天是放假最后一个工作日,还是打算在活动结束之前将本次更文活动的最后一篇文章搞定,届时就是在此次活动中写了7篇文章,也算是完成了第一关挑战了,更文不易,记得看完点赞!^_^

背景

这次也没有老规矩,背景无,但是也还是要闲扯几句才进入主题。那就是说明这次文章的主题为啥是聊递归?主要还是之前写了一篇水文,现在来还账来了;还有就是最近复习,想重新深入了解一下啥子是递归递归到底是个啥?嗯?感觉是同一个意思呢,那就换一个,递归到底怎么用,常见用到递归的场景有些啥?

好了,话不多说,请看下面浅见→

递归

什么是递归?

在程序中,递归就是函数自己直接或间接的调用自己。递归不能称得上是一种算法,而是应该理解为是一种符合人解题逻辑的编程技巧。比如数学中常见的问题:斐波那契数列、上楼梯、汉诺塔等问题,它们都能使用递归的思想那解决问题最终得到答案。

怎么理解递归?

要理解递归,那就必须知道什么是,毕竟递归需要利用这种数据结构来解决问题。那什么是呢?先看下面这张图,它就能很好表达栈的操作规则↓

image.png

栈是一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照先进后出的原则存储数据,先进入的被放入栈底(也称为进栈),后进入的数据放在栈顶,需要读数据的时候从栈顶开始弹出数据(弹出也可称为出栈或退栈)。

好了,概念了解了,那下面就要来理解递归了→

其实递归和普通函数调用没有什么区别,只是递归一般不能立即得到结果,而是需要经历挂起、入栈、出栈的过程才能解决问题。下面就用经典的斐波那契数列来演示递归的过程,如下:

斐波那契数列也称“兔子数列”,它是一个这样的数列:1,1,2,3,5,8,13,21,34,55,89...

// 斐波那契数列: 1 1 2 3 5 8 13 21 34 ... 
// 从第三项开始,后面一项的值为前面两项相加之和

第一项: 1 ..............................1
第二项: 1 ..............................1                 
第三项: 1 + 1 ..........................2
第四项: 1 + 2 ..........................3
第五项: 2 + 3 ..........................5
第六项: 3 + 5 ..........................8
第n项: (n - 2) + (n - 1) ...............n

转化为图,就是下图所示: image.png

将上述分析转化为代码,如下:

function fibonacci(n) { 
    if (n <= 2) { // 终止条件
        return 1; 
    } 
    return fibonacci(n - 2) + fibonacci(n - 1); 
}
fibonacci(6); // 8

执行上述代码就可以求得第N个斐波那契数值,光看代码和分析可能还不足够清晰,下面就分析第6项的值是如何求得的,如图:

image.png

分析:首先执行fibonacci(6)的时候会生成一个执行上下文,返回3 + fibonacci(5);接着需要调用fibonacci(5)再生成一个执行上下文,返回2 + fibonacci(4);接着需要调用fibonacci(4)再生成一个执行上下文,返回2 + fibonacci(3);接着需要调用fibonacci(3)再生成一个执行上下文,返回1 + fibonacci(2);接着执行fibonacci(2)fibonacci(1);由于fibonacci(1)处于栈顶,不需要往下运行了,就开始弹出,返回结果1,接着弹出fibonacci(2),并返回结果1给执行上下文fibonacci(3)fibonacci(3)执行就是1 + 1 = 2,处于栈顶不需要执行了就把结果传递下去给fibonacci(4)fibonacci(4)执行就是1 + 2 = 3,同样往下,最后到达fibonacci(6)弹出,最后结果就为8

这就是斐波那契数列的执行过程,因为前两项是固定的,所以将其设为终止条件,然后后面就依次类推计算第N项的值(注意:不是第N项的和!!!),最后得到第N项的值就等于前两项相加之和,即(n-2) + (n -1)

递归的要素

看懂了上面的示例,那就可以从上面知道,递归需要满足的要素主要有下面两个条件:

  1. 自己调用自己
  2. 要有结束条件

这里就有问题要问,既然递归了解了,那它是为了解决怎样的问题引出来的?其实递归就是用来解决复杂问题的思想,比如说要你计算1~100的数字之和,你用for循环来一个一个往后循环,可是你用递归你就只需为其设置一个终止条件,让其自身调用自己就能获得最后结果了。如下:

// 将求100转换为求99 
// 将求99转换为求98 
// ... 
// 将求2转换为求1 
// 求1结果就是1 
// 即:sum(1)是1 

function sum(n) { 
    if (n == 1) { 
        return 1; 
    } 
    return sum(n - 1) + n; 
} 
var num = sum(100); 
console.log(num); // 5050

递归的练习

来,光说不练假把式,下面就用几个示例试试水,如下:

  1. 求阶乘!分析及代码如下:
  • 1! .............1
  • 2! .............1! * 2
  • 3! .............2! * 3
  • ...
  • n! .............(n-1)! * n
function factorial(n) { 
    if ( n == 1) { 
        return 1; 
    } 
    return factorial(n - 1) * n; 
} 

// 5! = 1 * 2 * 3 * 4 * 5 
console.log(factorial(5)); // 120
  1. 求幂!分析及代码如下:
  • 1 的 m 次方 1 * 1^(m - 1) = 1^m = 1
  • 2 的 m 次方 m 个 2 相乘 2 * 2^(m - 1) = 2^m
  • ...
  • n 的 m 次方 m 个 n 相乘 即 n * n^(m - 1) = n^m
  • 每个数的 1 次方都等于它本身,如2^1 = 2; 3^1 = 3 等
function power(n, m) { 
    if (m == 1){ 
        return n; 
    } 
    return power(n, m-1) * n; 
} 

console.log(power(4,3)); // 4 * 4 * 4 = 48
  1. 最后来个实际项目中经常遇到的,将平铺数组转为树,如下:

数据集合为:

const arr = [
    {id: '1', parentId: 'root', name:'1'},
    {id: '1-1', parentId: '1', name:'1-1'},
    {id: '1-2', parentId: '1', name:'1-2'},
    {id: '1-1-1', parentId: '1-1', name:'1-1-1'},
    {id: '1-1-2', parentId: '1-1', name:'1-1-2'},
    {id: '1-2-1', parentId: '1-2', name:'1-2-1'}
];

function getTreeData(data, pid) {
    let copyData = JSON.parse(JSON.stringify(data));
    let arr = [];
    for (let i = 0; i < copyData.length; i++) {
        if (copyData[i].parentId == pid) {
            copyData[i].children = getTreeData(copyData, copyData[i].id);
            arr.push(copyData[i]);
        }
    }
    return arr;
}

const tree = getTreeData(arr, 'root');
console.log('tree', tree);

至此,递归的相关知识就介绍完了,希望可以通过上面的示例帮助彼此更好的理解递归这个思想。实际项目中其实用到递归的思想也没多少,这就是经常说的,面试是面试,工作是工作。工作中会考虑算法的时间复杂度等,那这时递归的方式不是那么优秀,在性能方面也不是特别好,就拿上面举的将平铺数据转化为树结构数据,其实项目中就会选用其他方式来实现,只是这里聊的是递归,就用递归思想来演示一下。

四月的更文活动就以这篇做完结了,真的太不轻松了,幸好能赶在结束之前完成挑战,后续也会继续更文的,最后祝大家五一玩得愉快,注意疫情哦!

xdm看文至此,点个赞👍再走哦,3Q^_^

往期精彩文章

后语

伙伴们,如果觉得本文对你有些许帮助,点个👍或者➕个关注在走呗^_^ 。另外如果本文章有问题或有不理解的部分,欢迎大家在评论区评论指出,我们一起讨论共勉。