JavaScript 数据结构与算法之美 - 你可能真的不懂递归

1. 前言
- 算法为王。
- 排序算法博大精深,前辈们用了数年甚至一辈子的心血研究出来的算法,更值得我们学习与推敲。
因为之后要讲有内容和算法,其代码的实现都要用到递归,所以,搞懂递归非常重要。
2. 定义
- 方法或函数调用自身的方式称为递归调用,调用称为递,返回称为归。
简单来说就是:自己调用自己。
现实例子:周末你带着女朋友去电影院看电影,女朋友问你,咱们现在坐在第几排啊 ?电影院里面太黑了,看不清,没法数,现在你怎么办 ?
于是你就问前面一排的人他是第几排,你想只要在他的数字上加一,就知道自己在哪一排了。
但是,前面的人也看不清啊,所以他也问他前面的人。
就这样一排一排往前问,直到问到第一排的人,说我在第一排,然后再这样一排一排再把数字传回来。
直到你前面的人告诉你他在哪一排,于是你就知道答案了。
基本上,所有的递归问题都可以用递推公式来表示,比如:
f(n) = f(n-1) + 1;
// 其中,f(1) = 1
f(n) 表示你想知道自己在哪一排,f(n-1) 表示前面一排所在的排数,f(1) = 1 表示第一排的人知道自己在第一排。
有了这个递推公式,我们就可以很轻松地将它改为递归代码,如下:
function f(n) {
if (n == 1) return 1;
return f(n-1) + 1;
}
3. 为什么使用递归 ?递归的优缺点 ?
- 优点:代码的表达力很强,写起来简洁。
- 缺点:空间复杂度高、有堆栈溢出风险、存在重复计算、过多的函数调用会耗时较多等问题。
4. 什么样的问题可以用递归解决呢 ?
一个问题只要同时满足以下 3 个条件,就可以用递归来解决。
- 问题的解可以分解为几个子问题的解。何为子问题 ?就是数据规模更小的问题。
比如,前面讲的电影院的例子,你要知道,自己在哪一排的问题,可以分解为前一排的人在哪一排这样一个子问题。 - 问题与子问题,除了数据规模不同,求解思路完全一样
比如电影院那个例子,你求解自己在哪一排的思路,和前面一排人求解自己在哪一排的思路,是一模一样的。 - 存在递归终止条件
比如电影院的例子,第一排的人不需要再继续询问任何人,就知道自己在哪一排,也就是 f(1) = 1,这就是递归的终止条件。
5. 递归常见问题及解决方案
- 警惕堆栈溢出:可以声明一个全局变量来控制递归的深度,从而避免堆栈溢出。
- 警惕重复计算:通过某种数据结构来保存已经求解过的值,从而避免重复计算。
6. 如何实现递归 ?
1. 递归代码编写
写递归代码的关键就是找到如何将大问题分解为小问题的规律,并且基于此写出递推公式,然后再推敲终止条件,最后将递推公式和终止条件翻译成代码。
2. 递归代码理解
对于递归代码,若试图想清楚整个递和归的过程,实际上是进入了一个思维误区。
那该如何理解递归代码呢 ?
- 如果一个问题 A 可以分解为若干个子问题 B、C、D,你可以假设子问题 B、C、D 已经解决。
- 而且,你只需要思考问题 A 与子问题 B、C、D 两层之间的关系即可,不需要一层层往下思考子问题与子子问题,子子问题与子子子问题之间的关系。
- 屏蔽掉递归细节,这样子理解起来就简单多了。
因此,理解递归代码,就把它抽象成一个递推公式,不用想一层层的调用关系,不要试图用人脑去分解递归的每个步骤。
7. 例子
1. 一个阶乘的例子:
function fact(num) {
if (num <= 1) {
return 1;
} else {
return num * fact(num - 1);
}
}
fact(3) // 结果为 6
以下代码可导致出错:
var anotherFact = fact;
fact = null;
alert(antherFact(4)); //出错
由于 fact 已经不是函数了,所以出错。
使用 arguments.callee
arguments.callee 是一个指向正在执行的函数的指针,arguments.callee 返回正在被执行的对现象。
新的函数为:
function fact(num){
if (num <= 1){
return 1;
}else{
return num * arguments.callee(num - 1); //此处更改了。
}
}
var anotherFact = fact;
fact = null;
alert(antherFact(4)); // 结果为 24
2. 再看一个多叉树的例子
先看图

叶子结点:就是深度为 0 的结点,也就是没有孩子结点的结点,简单的说就是一个二叉树任意一个分支上的终端节点。
数据结构格式,参考如下代码:
const json = {
name: 'A',
children: [
{
name: 'B',
children: [
{
name: 'E',
},
{
name: 'F',
},
{
name: 'G',
}
]
},
{
name: 'C',
children: [
{
name: 'H'
}
]
},
{
name: 'D',
children: [
{
name: 'I',
},
{
name: 'J',
}
]
}
]
}
我们如何获取根节点的所有叶子节点个数呢 ?
递归代码如下:
/**
* 获取根节点的所有 叶子节点 个数
* @param {Object} json Object 对象
*/
function getLeafCountTree(json) {
if(!json.children){
return 1;
} else {
let leafCount = 0;
for(let i = 0 ; i < json.children.length ; i++){
// leafCount = leafCount + getLeafCountTree(json.children[i]);
leafCount = leafCount + arguments.callee(json.children[i]);
}
return leafCount;
}
}
递归遍历是比较常用的方法,比如:省市区遍历成树、多叉树、阶乘等。
8. 最后
如果觉得本文还不错,记得给个 star , 你的 star 是我持续更新的动力!。
笔者 GitHub。
参考文章:
JavaScript 数据结构与算法之美 - 你可能真的不懂递归的更多相关文章
- JavaScript 数据结构与算法之美 - 线性表(数组、栈、队列、链表)
前言 基础知识就像是一座大楼的地基,它决定了我们的技术高度. 我们应该多掌握一些可移值的技术或者再过十几年应该都不会过时的技术,数据结构与算法就是其中之一. 栈.队列.链表.堆 是数据结构与算法中的基 ...
- JavaScript 数据结构与算法之美 - 十大经典排序算法汇总(图文并茂)
1. 前言 算法为王. 想学好前端,先练好内功,内功不行,就算招式练的再花哨,终究成不了高手:只有内功深厚者,前端之路才会走得更远. 笔者写的 JavaScript 数据结构与算法之美 系列用的语言是 ...
- JavaScript 数据结构与算法之美 - 栈内存与堆内存 、浅拷贝与深拷贝
前言 想写好前端,先练好内功. 栈内存与堆内存 .浅拷贝与深拷贝,可以说是前端程序员的内功,要知其然,知其所以然. 笔者写的 JavaScript 数据结构与算法之美 系列用的语言是 JavaScri ...
- JavaScript 数据结构与算法之美 - 冒泡排序、插入排序、选择排序
1. 前言 算法为王. 想学好前端,先练好内功,只有内功深厚者,前端之路才会走得更远. 笔者写的 JavaScript 数据结构与算法之美 系列用的语言是 JavaScript ,旨在入门数据结构与算 ...
- JavaScript 数据结构与算法之美 - 归并排序、快速排序、希尔排序、堆排序
1. 前言 算法为王. 想学好前端,先练好内功,只有内功深厚者,前端之路才会走得更远. 笔者写的 JavaScript 数据结构与算法之美 系列用的语言是 JavaScript ,旨在入门数据结构与算 ...
- JavaScript 数据结构与算法之美 - 桶排序、计数排序、基数排序
1. 前言 算法为王. 想学好前端,先练好内功,只有内功深厚者,前端之路才会走得更远. 笔者写的 JavaScript 数据结构与算法之美 系列用的语言是 JavaScript ,旨在入门数据结构与算 ...
- JavaScript 数据结构与算法之美 - 非线性表中的树、堆是干嘛用的 ?其数据结构是怎样的 ?
1. 前言 想学好前端,先练好内功,内功不行,就算招式练的再花哨,终究成不了高手. 非线性表(树.堆),可以说是前端程序员的内功,要知其然,知其所以然. 笔者写的 JavaScript 数据结构与算法 ...
- 《数据结构与算法之美》 <04>链表(上):如何实现LRU缓存淘汰算法?
今天我们来聊聊“链表(Linked list)”这个数据结构.学习链表有什么用呢?为了回答这个问题,我们先来讨论一个经典的链表应用场景,那就是 LRU 缓存淘汰算法. 缓存是一种提高数据读取性能的技术 ...
- javascript数据结构与算法--高级排序算法
javascript数据结构与算法--高级排序算法 高级排序算法是处理大型数据集的最高效排序算法,它是处理的数据集可以达到上百万个元素,而不仅仅是几百个或者几千个.现在我们来学习下2种高级排序算法-- ...
随机推荐
- 配置没有问题,虚拟机Ubuntu系统ifconfig没有网卡信息
如果没有问题,前几天都好好的,突然出现这个问题 sudo ifconfig etho up 其中eth0是我的网卡名称
- 原创:用node.js搭建本地服务模拟接口访问实现数据模拟
前端开发中,数据模拟是必要的,这样就能等后台接口写完,我们直接把接口请求的url地址从本地数据模拟url换成后台真实地址就完成项目了.传参之类的都不用动. 之前网上找了很多类似于mock等感觉都不太实 ...
- codeforces 327 B. Hungry Sequence
题目链接 题目就是让你输出n个数的序列,要保证该序列是递增的,并且第i个数的前面不能保护它的约数,我直接先对前100000的素数打表,然后输出前n个,so easy. //cf 191 B #incl ...
- 基于spring的观察者模式
简单的说,观察者模式,就类似于 广播站发送广播,和收音机的关系.多个收音机去收听同一个广播频道. 在实际的业务场景中,可以是这样的.创建订单成功后,发布事件.然后减库存.发送短信.调用微信.调用物流服 ...
- java中System.out.println()打印输出结果
疑点:syso()是打印输出语句,打印的是什么? syso()不同情况下打印输出的结果不一样: 1. package com.briup; public class Syso { public sta ...
- 『开发技术』Windows极简安装使用face_recognition
face_recognition是一个强大.简单.易上手的人脸识别开源项目,并且配备了完整的开发文档和应用案例,特别是兼容树莓派系统.此项目是世界上最简洁的人脸识别库,你可以使用Python和命令行工 ...
- 夯实Java基础(四)——面向对象之多态
1.多态介绍 面向对象三大特征:封装.继承.多态.多态是Java面向对象最核心,最难以理解的内容.从一定角度来看,封装和继承几乎都是为多态而准备的. 多态就是指程序中定义的引用变量所指向的具体类型和通 ...
- 值得花费一周研究的算法 -- KMP算法(indexOf)
KMP算法是由三个科学家(kmp分别是他们名字的首字母)创造出来的一种字符串匹配算法. 所解决的问题: 求文本字符串text内寻找第一次出现字符串s的下标,若未出现返回-1. 例如 text : &q ...
- java8(一)Lambda表达式
其实很久前已经学习过了Lambda表达式,但是学习后没有多少使用的机会,久而久之也就忘记(惭愧).最近新的项目用的jdk8所以准备再学习一次,写下文章也是为了记录,方便以后再忘的时候,不用到处找资料( ...
- Mac安装Homebrew的那些事儿
Mac安装Homebrew的那些事儿 最近小明刚换置了一个 Mac 本,想搭建一个属于自己的博客网站,需要用到 Node.js 环境,而Node.js 在 MacOS 中是由 Homebrew 进行安 ...