一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情。
前言
四月的更文挑战活动真是让人太卷了,恰好又是临近五一放假赶上公司最忙的时刻,每天都是忙得飞起,更别说写文章的时间了。但是老话说:时间就像海绵,挤一挤还是有的
。这不,紧赶慢赶还是陆陆续续写了6
篇文章了,感兴趣的可以滑到文章底部查看往期精彩文章!
今天是放假最后一个工作日,还是打算在活动结束之前将本次更文活动的最后一篇文章搞定,届时就是在此次活动中写了7
篇文章,也算是完成了第一关挑战了,更文不易,记得看完点赞!^_^
背景
这次也没有老规矩,背景无,但是也还是要闲扯几句才进入主题。那就是说明这次文章的主题为啥是聊递归?主要还是之前写了一篇水文,现在来还账来了;还有就是最近复习,想重新深入了解一下啥子是递归
?递归
到底是个啥?嗯?感觉是同一个意思呢,那就换一个,递归
到底怎么用,常见用到递归
的场景有些啥?
好了,话不多说,请看下面浅见→
递归
什么是递归?
在程序中,递归就是函数自己直接或间接的调用自己。递归
不能称得上是一种算法,而是应该理解为是一种符合人解题逻辑的编程技巧。比如数学中常见的问题:斐波那契数列、上楼梯、汉诺塔等问题,它们都能使用递归的思想那解决问题最终得到答案。
怎么理解递归?
要理解递归
,那就必须知道什么是栈,毕竟递归需要利用栈
这种数据结构来解决问题。那什么是栈
呢?先看下面这张图,它就能很好表达栈的操作规则↓
栈是一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照先进后出的原则存储数据,先进入的被放入栈底(也称为进栈
),后进入的数据放在栈顶,需要读数据的时候从栈顶开始弹出数据(弹出也可称为出栈或退栈
)。
好了,概念了解了,那下面就要来理解递归了→
其实递归
和普通函数调用没有什么区别,只是递归
一般不能立即得到结果,而是需要经历挂起、入栈、出栈的过程才能解决问题。下面就用经典的斐波那契数列来演示递归的过程,如下:
斐波那契数列
也称“兔子数列”
,它是一个这样的数列: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
转化为图,就是下图所示:
将上述分析转化为代码,如下:
function fibonacci(n) {
if (n <= 2) { // 终止条件
return 1;
}
return fibonacci(n - 2) + fibonacci(n - 1);
}
fibonacci(6); // 8
执行上述代码就可以求得第N个斐波那契数值,光看代码和分析可能还不足够清晰,下面就分析第6
项的值是如何求得的,如图:
分析:首先执行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~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
- 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 的 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
- 最后来个实际项目中经常遇到的,将平铺数组转为树,如下:
数据集合为:
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^_^
往期精彩文章
- 炒冷饭系列4:JavaScript中的作用域和闭包
- 炒冷饭系列3:面试你必须准备构造函数、原型、原型链和继承的相关知识!
- 炒冷饭系列2:来看看面试题中的Javascript事件循环机制!
- 炒冷饭系列1:一道字节面试题引出的this指向问题
- GitHub Copilot体验:你的人工智能结对程序员来啦!
- 如何使用React-sortable-hoc和React-draggable拖拽组件实现产品需求?
- 聊聊前端如何实现复制粘贴功能及Github上提请代码PR的具体流程?
- (建议收藏)快来看看最值得拥有且最详细的Git使用教程
- (建议收藏)快来看看我们团队是如何制定前端开发规范的
- (建议收藏)前端开发中常见的浏览器兼容性问题及解决方案大汇总
- 如何在前端项目中对页面元素进行放大缩小操作?
- 聊聊如何在React项目中使用Antd的Table组件实现Echarts的热力图效果?
- 如何在Vue或React项目中引入外部字体并使用?
- 如何在React项目中使用高德地图插件并封装弹窗组件呢?
- 数据可视化-如何在React项目中使用Echarts插件并封装图表组件?
- 快来看看我是如何更改Antd中DatePicker周选择器默认设置的?
- 如何封装Vue水印组件和 React中如何使用水印组件?
- 最强富文本编辑器?TinyMCE系列文章【3】
- 最强富文本编辑器?TinyMCE系列文章【2】
- 最强富文本编辑器?TinyMCE系列文章【1】
- 在React项目中实现仿饿了么Checkbox多选按钮样式的效果组件
- 2022第一次更文:前端项目实战中的3种Progress进度条效果
- 2022年前端技术趋势:你不进来看看,怎么赚它一个小目标?
- 假如古代有程序员写总结,大概就是这样吧 | 2021年终总结
后语
伙伴们,如果觉得本文对你有些许帮助,点个👍或者➕个关注在走呗^_^ 。另外如果本文章有问题或有不理解的部分,欢迎大家在评论区评论指出,我们一起讨论共勉。