简单易懂的现代魔法-递归

news/2025/2/27 10:32:37

平时在前端开发中,好像也没啥用到递归的地方。不过这并不代表递归不重要,如果你看过一些框架的源码,就会经常见到它的影子:比如渲染虚拟DOM的render函数,webpack中require依赖分析,Koa2洋葱式的中间件模型,其实都运用到了递归算法。

博客原文

递归概念

那么递归到底是啥?先上两张图:

图1:

图2:

递归,就是在运行的过程中调用自己

我们来看个最简单的阶乘函数:

5! = 5 * 4 * 3 * 2 * 1
复制代码
function factorial(num) {
    if (num === 1) { // 基线条件
        return 1;
    }

    // 递归条件
    return num * factorial(num-1);
}

factorial(5);
复制代码

一个常规的递归函数都有两部分:

  1. 基线条件(if (num === 1)):保证函数不再调用自己,避免无限循环
  2. 递归条件(num * factorial(num-1)):保证函数能够调用自己

调用栈

栈是一种先进后出的数据结构,它只有两种操作,出栈和入栈

const nekopara = ['chocolat', 'Coconut'];
nekopara.push('vanilla'); // 入栈
nekopara.pop(); // 出栈
复制代码

代码在运行过程中,会有一个叫做调用栈(call stack)的概念。

function greet(name) {
    console.log(`hello, ${name}!`)
    greet2(name);
    console.log(`getting ready to say bye`);
    bye();
}

function greet2(name) {
    console.log(`how are you, ${name}?`);
}

function bye() {
    console.log(`bye`);
}

greet('deepred');
复制代码

调用greet('deepred')时,计算机会首先给该函数分配一块内存,并将变量名name设置为deepred

每当调用函数时,都会分配一个内存块并将涉及到的变量值存储到内存中。

打印hello, deepred后,调用了greet2('deepred')。同样,计算机再次分配了一块内存,并且该内存块位于第一个内存块上面。

调用栈的最上面表示当前运行的函数,如图所示,现在正在运行的是greet2函数,打印输出how are you, deepred?后,函数greet2执行完毕,栈顶的内存块被弹出。

现在栈顶的内存块又变回greet,这意味着我们从greet2的函数中跳出,再次返回到了greet。

我们在greet中调用了greet2时,greet只执行了一部分。

特别注意:调用另外一个函数时,当前函数暂停并且处于未完成状态,暂停函数的所有变量的值仍然在内存中

执行完greet2后,我们回到了greet,并从离开的地方开始接着往下执行:首先打印getting ready to say bye,然后调用bye函数。

在栈顶添加了bye函数的内存块后,开始执行bye函数,打印bye,然后函数返回,内存块被弹出。

我们又再次回到了greet中,这次没有其他要运行的代码了,于是从greet函数中返回,内存块被弹出,调用栈最后为空。

完整的一次调用流程

递归调用栈

递归同样使用调用栈

我们来分析下阶乘fact(3)的调用栈

function fact(num) {
   if (num === 1) { 
       return 1;
   }
   return num * fact(num-1);
}

fact(3);
复制代码

直接看图:

递归注意事项

递归会导致程序的性能变低

如果递归嵌套很深,那么调用栈会很长,这将占用大量内存,可能会导致栈溢出


http://www.niftyadmin.cn/n/975970.html

相关文章

在vscode用Markdown

一.在vscode官网中先下载vscode vscode官网 在官网中下载 一直按next,注意安装路径设置、环境变量默认自动添加到系统中,勾选以下所有选项: 二.安装后下载插件 Markdown要用的插件为Markdown-Notes-Pack,点击install,安装完成…

依赖注入基本原理

2019独角兽企业重金招聘Python工程师标准>>> 前言 在软件工程领域,依赖注入(Dependency Injection)是用于实现控制反转(Inversion of Control)的最常见的方式之一。本文主要介绍依赖注入原理和常见的实现方…

c语言数组升序排列,数组输入各数之间用空格隔开,输出用空格隔开

目录 输入用空格隔开的实现方法&#xff1a; 输出各数之间用空格隔开&#xff1a; 升序&#xff1a; 整个代码&#xff1a; 输入用空格隔开的实现方法&#xff1a; for(i0;i<n;i){scanf("%d",&s);//先用一个数得到输入结果char cgetchar();//回收缓存区的回…

IDEA导入JDK出现The selected directory is not a valid home for JDK的解决方法

问题原因 选择的文件一定是jdk1.8.0_101&#xff0c;不能改名&#xff0c;若已经改名&#xff0c;请用卸载安装大法&#xff0c;下载时一切按原有路径进行&#xff0c;默认安装位置就是这种jdk1.8.0_101命名形式的。 如何卸载干净 在电脑设置中搜索添加或删除应用程序&#…

索引为何不可用

通过索引扫描的记录数超过30%&#xff0c;变成全表扫描。联合索引中&#xff0c;第一个索引列使用范围查询&#xff08;这时用到部分索引&#xff09;。联合索引中&#xff0c;第一个查询条件不是最左索引列。模糊查询条件列最左以通配符%开始。HEAP表使用HASH索引时&#xff0…

idea报错:java: 错误: 无效的源发行版:16解决办法

文件中点击项目结构&#xff08;file>Project Structure&#xff09; 选择项目&#xff08;project&#xff09; 选这个版本&#xff08;汉化&#xff09; &#xff08;原版&#xff09;

关于HTML标签嵌套的问题详解

先说基础&#xff0c;HTML标签有两类&#xff1a; 1、块级元素 div、h1~h6、address、blockquote、center、dir、dl、dt、dd、fieldset、form、hr、isindex、menu、noframes、noscript、ol、p、pre、table、ul…… 特点&#xff1a;总是在新行上开始&#xff0c;高度、行高以及…

c语言,蓝桥杯,十六进制转十进制

文章目录完整代码算法解释pow&#xff08;&#xff09;用来求x的y次方十六进制转十进制的方法完整代码 #include<stdio.h> #include<string.h> #include<math.h> int main(){ char strHex[10]; int i 0;//循环用 int len 0;//保存字符长度 int te…