理解JavaScript递归

什么是递归

程序调用自身的编程技巧称为递归(recursion)

递归的基本思想是将一个复杂的问题分解成更小、更易于管理的子问题,这些子问题与原始问题相似,但规模更小。

递归的要素

  • 基本情况(Base Case):
    这是递归终止的条件,也就是说,当问题足够小,可以直接解决而不需要进一步递归时,就会返回一个直接的答案。

  • 递归步骤(Recursive Step):
    在这一步中,问题被分解成更小的子问题,并且递归地解决这些子问题。每个递归调用都向基本情况更进一步。

递归与循环的比较

递归和循环是实现重复操作的两种基本方法。它们在不同的场景下各有优势。

场景

  • 递归适合解决可以分解为逐渐缩小的子问题的问题。
  • 循环通常用于需要重复执行固定次数的操作,或者在满足特定条件之前重复执行。

性能

  • 递归由于每次调用都会占用新的栈空间,可能会导致栈溢出,特别是在深度递归时。此外,递归的执行效率通常低于循环,因为它涉及更多的函数调用开销。
  • 循环通常更高效,因为它避免了函数调用的开销,并且可以更直接地控制循环的次数。

限制

  • 递归需要有明确的终止条件,否则会导致无限递归。
  • 循环也需要有明确的终止条件,否则也可能导致无限循环。

阶乘

递归实现阶乘:

基本情况:
基本情况就是结束条件,这可以有很多,比如:
n = 1 时,返回 1

n = 2 时,返回 2 (1 * 2

n = 3 时,返回 6 (1 * 2 * 3

我们使用最简单的结束条件:
n = 1 时,返回 1

递归步骤:
每次递归调用时,都会将之前的结果乘以当前数字,然后返回结果。

实现:

function factorial(n) {if (n == 1) return n;return n * factorial(n - 1);
}console.log(factorial(5)); // 5 * 4 * 3 * 2 * 1 = 120

通过下图,可以看出这个递归函数的实现原理:

循环实现阶乘:

function factorial(n) {let result = 1; for (let i = 2; i <= n; i++) {result *= i;}return result;
}console.log(factorial(5)); // 输出: 120

斐波那契数列

斐波那契数列是一个特殊的数列,其中每个数都是前两个数的和。

1, 1, 2, 3, 5, 8, 13, 21, 34, ...

下来我们实现一个函数,返回斐波那契数列的第 n 个数。

斐波那契数列的递归实现:

基本情况:

如果 n01 ,直接返回 n

递归步骤:

每次递归调用都返回前两个数的和。

实现:

function fibonacci(n) {if (n === 0) return 0;if (n === 1) return 1;return fibonacci(n - 1) + fibonacci(n - 2);
}// 计算第6项斐波那契数
console.log(fibonacci(6)); // 输出: 8

斐波那契数列的循环实现:

function fibonacci(n) {// 初始化数组,包含前两项let fib = [1, 1];for (let i = 2; i < n; i++) {// 计算当前项,即前两项的和fib[i] = fib[i - 1] + fib[i - 2];}return fib[n - 1];
}// 计算第6项斐波那契数
console.log(fibonacci(6)); // 输出: 8

尾递归

我们看这个斐波那契数列的递归实现,他的函数调用次数要比循环次数多很多。我们打印出来看下。

let count = 0;
function fibonacci(n) {count++;if (n === 0) return 0;if (n === 1) return 1;return fibonacci(n - 1) + fibonacci(n - 2);
}// 计算第6项斐波那契数
console.log(fibonacci(6)); // 输出: 8
console.log(`调用 ${count}`); // 输出: 调用 25 次

函数执行一次,就会创建一个执行上下文,并且压入执行上下文栈,当函数执行完毕的时候,就会将函数的执行上下文从栈中弹出。我们这个递归函数,创建很多执行上下文压入执行上下文栈。

递归次数过多还会导致栈溢出。

chrome 浏览器的 Sources 面板中,我们通过 Call Stack 可以看到执行上下文的函数调用栈的具体情况

下来我们开始使用尾递归优化这个递归函数。

尾递归是指递归函数内部的最后一个动作是函数调用。该调用的返回值,直接返回给函数。
这样就可以避免创建新的栈帧。

我们直接将上一次的结果,作为参数传递给下一次的函数调用。

function fibonacci(n, ac1 = 1, ac2 = 1) {if (n == 1 || n == 2) {return ac2;}return fibonacci(n - 1, ac2, ac1 + ac2);
}// 计算第6项斐波那契数
console.log(fibonacci(6)); // 输出: 8

注意:
上面的 fibonacci 函数虽然已经使用了尾递归优化,但还是可能出现栈溢出。

因为 JavaScript 引擎并不总是能够优化尾递归调用以避免栈溢出。这是因为尾递归优化( Tail Call Optimization , TCO )并不是 JavaScript 语言规范的一部分,也不是所有 JavaScript 引擎都实现了这一优化。

在没有 TCO 的环境中,每次递归调用仍然会占用新的栈空间,fibonacci 函数的每次调用都需要保留其执行上下文(包括参数和局部变量),直到该调用的返回值被使用。随着 n 的增加,递归调用的深度也会增加,这最终可能导致栈溢出。

递归应用的业务场景

数组转树

比如在做权限控制、级联选择组件的时候,后端返回的数据可能是数组,前端需要将其转换为树形结构。这时可以使用递归来解决。

JavaScript 实现扁平数组与树结构的相互转换

递归组件

在 Vue 中,递归组件是一种可以调用自身作为其子组件的组件。递归组件非常适合用来展示嵌套的或者树状的数据结构,例如文件系统、组织结构图、菜单列表等。

在做侧边栏可能有多个层级的时候,可以使用递归组件。

vue-element-admin 项目中的 SidebarItem 组件 就是使用了递归组件。

总结

使用递归解决问题,重点是如果把问题分解为更小的相同问题,确定递归终止条件,然后递归调用自身。
使用递归要注意栈溢出问题,可以考虑使用尾递归优化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/12903.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024年第十届中西部外语翻译大赛

2024年第十届中西部外语翻译大赛 竞赛信息 “由中西部翻译协会共同体指导发起&#xff0c;各省市译协共建学术指导委员会&#xff0c;2024年第十届中西部外语翻译大赛由中西部翻译协会共同体秘书处&#xff08;武汉公仪网络科技有限公司&#xff09;承办。” - 获奖证书样图 -…

开发板连接电机,烧坏芯片的原因、解决

当使用开发板、核心板&#xff0c;连接电机驱动板&#xff0c;控制电机的转动&#xff0c;会很容易烧芯片。 极少数是通电就烧坏&#xff0c;有些是调试了一段时间才烧&#xff0c;也有些是稳定运行好些日子突然烧了...... 百度搜索&#xff1a;“STM32 电机 烧坏”&#xff…

python文件操作常用方法(读写txt、xlsx、CSV、和json文件)

引言 用代码做分析的时候经常需要保存中间成果&#xff0c;保存文件格式各不相同&#xff0c;在这里好好总结一下关于python文件操作的方法和注意事项 Python 提供了丰富的文件操作功能&#xff0c;允许我们创建、读取、更新和删除文件。允许程序与外部世界进行交互。 文章目录…

【C++】从零开始构建二叉搜索树

送给大家一句话&#xff1a; 我们始终有选择的自由。选错了&#xff0c;只要真诚的反思&#xff0c;真诚面对&#xff0c;也随时有机会修正错误和选择。 – 《奇迹男孩(电影)》 &#x1f4bb;&#x1f4bb;&#x1f4bb;&#x1f4bb;&#x1f4bb;&#x1f4bb;&#x1f4bb;…

数据资源入表难在哪?今晚带你一一弄懂(文末有福利)

​本周&#xff0c;我们即将开启数据要素系列直播《星光对话》的第四期&#xff0c;将由讲师-星光数智首席数据架构师 魏战松&#xff0c;于今晚19:00带来《数据资源入表和运营路径》的主题分享。 精彩内容提前知&#xff1a; 1、入表流程及各阶段参与方 2、入表难点和注意事项…

Android中使用Palette让你的页面UI优雅起来

文章目录 1. 什么是Palette2. 引入Palette3. 使用 Palette3.1 同步方式3.2 异步方式3.3 获取色调值 4. 举例4.1 布局文件 activity_palette_list.xml ⬇️4.2 Activity&#xff1a;PaletteListActivity⬇️4.3 列表Adapter&#xff1a;PaletteListAdapter ⬇️4.4 列表item布局…

「Python绘图」绘制同心圆

python 绘制同心圆 一、预期结果 二、核心代码 import turtle print("开始绘制同心圆") # 创建Turtle对象 pen turtle.Turtle() pen.shape("turtle") # 移动画笔到居中位置 pen.pensize(2) #设置外花边的大小 # 设置填充颜色 pen.fillcolor("green&…

java 并发线程应用

java 并发线程相关 线程状态 新建(NEW): 创建后尚未启动。可运行(RUNABLE): 正在 Java 虚拟机中运行。但是在操作系统层面,它可能处于运行状态,也可能等待资源调度(例如处理器资源),资源调度完成就进入运行状态。所以该状态的可运行是指可以被运行,具体有没有运行要看底层…

【C++算法】堆相关经典算法题

1.最后一块石头的重量 其实就是一个模拟的过程&#xff1a;每次从石堆中拿出最大的元素以及次大的元素&#xff0c;然后将它们粉碎&#xff1b;如果还有剩余&#xff0c;就将剩余的石头继续放在原始的石堆里面重复上面的操作&#xff0c;直到石堆里面只剩下一个元素&#xff0c…

[C/C++] -- 装饰器模式

装饰器模式是一种结构型设计模式&#xff0c;它允许在不改变原始对象的基础上动态地扩展其功能。这种模式通过将对象包装在装饰器类的对象中来实现&#xff0c;每个装饰器对象都包含一个原始对象&#xff0c;并可以在调用原始对象的方法之前或之后执行一些额外的操作。 装饰器…

自学C语言能达到什么境界呢?

C 语言是一门广泛应用于系统软件、嵌入式系统、游戏开发等领域的编程语言。那么&#xff0c;通过自学 C 语言&#xff0c;能够达到什么样的境界呢&#xff1f; 就像学习小提琴一样&#xff0c;仅凭自学也可以达到一定的水平&#xff0c;能够自娱自乐&#xff0c;在亲友聚会时表…

Xed编辑器开发第一期:使用Rust从0到1写一个文本编辑器

这是一个使用Rust实现的轻量化文本编辑器。学过Rust的都知道&#xff0c;Rust 从入门到实践中间还隔着好几个Go语言的难度&#xff0c;因此&#xff0c;如果你也正在学习Rust,那么恭喜你&#xff0c;这个项目被你捡到了。本项目内容较多&#xff0c;大概会分三期左右陆续发布&a…

NAS导航面板Sun-Panel

什么是 Sun-Panel &#xff1f; Sun-Panel 是一个服务器、NAS 导航面板、Homepage、浏览器首页。 软件主要特点&#xff1a; &#x1f349; 界面简洁&#xff0c;功能强大&#xff0c;资源消耗低&#x1f34a; 简单易用&#xff0c;可视化操作&#xff0c;零代码使用&#x1f…

python怎么安装matplotlib

1、登陆官方网址“https://pypi.org/project/matplotlib/#description”&#xff0c;下载安装包。 2、选择合适的安装包&#xff0c;下载下来。 3、将安装包放置到python交互命令窗口的当前目录下。 4、打开windows的命令行窗口&#xff0c;通过"pip install"这个命令…

新质生产力之工业互联网产业链

随着全球经济的数字化转型&#xff0c;新基建的概念逐渐成为推动工业发展的关键动力。在这一转型过程中&#xff0c;工业互联网作为新基建的核心组成部分&#xff0c;正逐渐塑造着未来工业的面貌。那么工业互联网产业链是如何构成的&#xff0c;以及它如何成为推动工业4.0和智能…

CRMEB开源打通版/标准版v4电商商城系统小程序发布之后无法生成海报问题

小程序产品分销二维码生成不了 开发者工具可以生成海报&#xff0c;但是发布之后无法生成 1.在开发者工具中&#xff0c;将不校验合法域名关闭 2.点击生成海报&#xff0c;查看console 3.将域名填写到微信公众平台小程序的download合法域名中 网址微信公众平台

react18【系列实用教程】memo —— 缓存组件 (2024最新版)

memo 的语法 如上图所示&#xff0c;在react中&#xff0c;当父组件重新渲染时&#xff0c;子组件也会重新渲染&#xff0c;即便子组件无任何变化&#xff0c;通过 memo 可以实现对组件的缓存&#xff0c;即当子组件无变化时&#xff0c;不再重新渲染子组件&#xff0c;核心代码…

【深度学习】Diffusion扩散模型的逆扩散问题

1、前言 上一篇&#xff0c;我们讲了Diffusion这个模型的原理推导。但在推导中&#xff0c;仍然遗留了一些问题。本文将解决那些问题 参考论文&#xff1a; ①Variational Diffusion Models (arxiv.org) ②Tutorial on Diffusion Models for Imaging and Vision (arxiv.org…

迭代的难题:敏捷团队每次都有未完成的工作,如何破解?

各位是否遇到过类似的情况&#xff1a;每次迭代结束后&#xff0c;团队都有未完成的任务&#xff0c;很少有完成迭代全部的工作&#xff0c;相反&#xff0c;总是将上期未完成的任务重新挪到本期计划会中&#xff0c;重新规划。敏捷的核心之一是“快速迭代&#xff0c;及时反馈…