如何优雅的使用javascript递归画一棵结构树

article/2025/10/16 8:09:52

640?wx_fmt=other

递归和尾递归

简单的说,递归就是函数自己调用自己,它作为一种算法在程序设计语言中广泛应用。其核心思想是把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。一般来说,递归需要有边界条件、递归前进阶段和递归返回阶段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。

但是作为一个合格的程序员,我们也应该知道,递归算法相对常用的算法如普通循环等,运行效率较低。因此,应该尽量避免使用递归,除非没有更好的算法或者某种特定情况,递归更为适合的时候。在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储,递归次数过多容易造成栈溢出等。

这个时候,我们就需要用到尾递归,即一个函数中所有递归形式的调用都出现在函数的末尾,对于尾递归来说,由于只存在一个调用记录,所以永远不会发生"栈溢出"错误。

举个例子,我们来实现一下阶乘,如果用普通的递归,实现将是这样的:

function factorial(n) {	if (n === 1) return 1;	return n * factorial(n - 1);	
}	factorial(5) // 120

最多需要保存n个调用栈,复杂度 O(n),如果我们使用尾递归:

function factorial(n, total) {	if (n === 1) return total;	return factorial(n - 1, n * total);	
}	factorial(5) // 120

此时只需要保存一个调用栈,复杂度 O(1) 。通过这个案例,你是否已经慢慢理解其精髓了呢?接下来我将介绍几个常用的递归应用的案例,并在其后实现本文标题剖出的树的实现。

递归的常用应用案例1. 数组求和

对于已知数组arr,求arr各项之和。

function sumArray(arr, total) {	if(arr.length === 1) {	return total	}	return sum(arr, total + arr.pop())	
}	let arr = [1,2,3,4];	
sumArray(arr, arr[1]) // 10

该方法给函数传递一个数组参数和初始值,也就是数组的第一项,通过迭代来实现数组求和。

2. 斐波那契数列

斐波那契数列(Fibonacci sequence),又称黄金分割数列,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波那契数列以如下被以递推的方法定义:F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)在现代物理、准晶体结构、化学等领域,斐波纳契数列都有直接的应用。接下来我们用js实现一个求第n个斐波那契数的方法:

// 斐波那契数列	
function factorial1 (n) {	if(n <= 2){	return 1	}	return factorial1(n-1) + factorial1(n-2)	
}	// 尾递归优化后	
function factorial2 (n, start = 1, total = 1) {	if(n <= 2){	return total	}	return factorial2 (n -1, total, total + start)	}

由尾递归优化后的函数可以知道,每一次调用函数自身,都会将更新后的初始值和最终的结果传递进去,通过回溯来求得最终的结果。

3. 阶乘

阶乘在上文已提到过,如想回顾,请向上翻阅。

4. 省市级联多级联动

省市级联多级联动的方法本质是生成结构化的数据结构,在element或antd中都有对应的实现,这里就不做过多介绍了。

5. 深拷贝

深拷贝的例子大家也已经司空见惯了,这里只给出一个简单的实现思路:

function clone(target) {	if (typeof target === 'object') {	let cloneTarget = Array.isArray(target) ? [] : {};	for (const key in target) {	cloneTarget[key] = clone(target[key]);	}	return cloneTarget;	} else {	return target;	}	
};

6. 爬梯问题

一共有n个台阶,每次只能走一个或两个台阶,问要走完这个台阶,一共有多少种走法。

n =1; result = 1  --> 1	
n =2; result = 2  --> 11 2	
n =3; result = 3  --> 111 12 21	
...	
如果第一步走1个台阶,由以上规律可以发现剩下的台阶有n-1种走法;	
如果第一步走2个台阶,由以上规律可以发现剩下的台阶有n-2种走法;	
则一共有fn(n-1) + fn(n-2) 种走法	
function steps(n) {	if(n <= 1) {	return 1	}	return steps(n-1) + steps(n-2)	
}

7. 对象数据格式化

这道题是本人曾经面试阿里的一道笔试题,问题是如果服务器返回了嵌套的对象,对象键名大小写不确定,如果统一让键名小写。

let obj = {	a: '1',	b: {	c: '2',	D: {	E: '3'	}	}	
}	
转化为如下:	
let obj = {	a: '1',	b: {	c: '2',	d: {	e: '3'	}	}	
}	// 代码实现	
function keysLower(obj) {	let reg = new RegExp("([A-Z]+)", "g");	for (let key in obj) {	if (obj.hasOwnProperty(key)) {	let temp = obj[key];	if (reg.test(key.toString())) {	// 将修改后的属性名重新赋值给temp,并在对象obj内添加一个转换后的属性	temp = obj[key.replace(reg, function (result) {	return result.toLowerCase()	})] = obj[key];	// 将之前大写的键属性删除	delete obj[key];	}	// 如果属性是对象或者数组,重新执行函数	if (typeof temp === 'object' || Object.prototype.toString.call(temp) === '[object Array]') {	keysLower(temp);	}	}	}	return obj;	
};

具体过程和思路在代码中已经写出了注释,感兴趣可以自己研究一下。

8. 遍历目录/删除目录

我们这里使用node来实现删除一个目录,用现有的node API确实有删除目录的功能,但是目录下如果有文件或者子目录,fs.rmdir && fs.rmdirSync 是不能将其删除的,所以要先删除目录下的文件,最后再删除文件夹。

function deleteFolder(path) {	var files = [];	if(fs.existsSync(path)) { // 如果目录存在	files = fs.readdirSync(path);	files.forEach(function(file,index){	var curPath = path + "/" + file;	if(fs.statSync(curPath).isDirectory()) { // 如果是目录,则递归	deleteFolder(curPath);	} else { // 删除文件	fs.unlinkSync(curPath);	}	});	fs.rmdirSync(path);	}	
}

9. 绘制分形图形

通过递归,我们可以在图形学上有更大的自由度,但是请记住,并不是最好的选择。

640?wx_fmt=other

640?wx_fmt=other

我们可以借助一些工具和递归的思想,实现如上的分形图案。

10. 扁平化数组Flat

数组拍平实际上就是把一个嵌套的数组,展开成一个数组,如下案例:

let a = [1,2,3, [1,2,3, [1,2,3]]]	
// 变成	
let a = [1,2,3,1,2,3,1,2,3]	
// 具体实现	
function flat(arr = [], result = []) {	arr.forEach(v => {	if(Array.isArray(v)) {	result = result.concat(flat(v, []))	}else {	result.push(v)	}	})	return result	
}	flat(a)

当然这只是笔者实现的一种方式,更多实现方式等着你去探索。

用递归画一棵自定义风格的结构树

通过上面的介绍,我想大家对递归及其应用已经有一个基本的概念,接下来我将一步步的带大家用递归画一棵结构树。效果图:

640?wx_fmt=other

640?wx_fmt=other

该图形是根据目录结构生成的目录树图,在很多应用场景中被广泛使用,接下来我们就来看看他的实现过程吧:

const fs = require('fs')	
const path = require('path')	
// 遍历目录/生成目录树	
function treeFolder(path, flag = '|_') {	var files = [];	if(fs.existsSync(path)) {	files = fs.readdirSync(path);	files.forEach(function(file,index){	var curPath = path + "/" + file;	if(fs.statSync(curPath).isDirectory()) { // recurse	// obj[file] = treeFolder(curPath, {});	console.log(flag, file)	treeFolder(curPath, '   ' + flag)	} else {	// obj['--'] = file	console.log(flag, file)	}	})	// return obj	}	
}	treeFolder(path.resolve(__dirname, './test'))

test为我们建的测试目录,如下:

640?wx_fmt=other

我们通过短短10几行代码就实现了一个生成结构树的小应用,是不是感觉递归有点意思呢?在这个函数中,第一个参数是目录的绝对路径,第二个是标示符,标示符决定我们生成的树枝的样式,我们可以自定义不同的样式。

欢迎大家相互学习交流,一起探索前端的边界。

更多推荐



欢迎关注下方公众号,获取更多前端知识精粹和学习社群:


在公众号点击进群,可以加入vue学习小组,一起学习前端技术;

回复 学习路径,将获取笔者多年从业经验的前端学习路径的思维导图

趣谈前端

Vue、React、小程序、Node 

前端 算法|性能|架构|安全


http://chatgpt.dhexx.cn/article/kkCge9yg.shtml

相关文章

python数据结构和算法 时间复杂度分析 乱序单词检测 线性数据结构 栈stack 字符匹配 表达式求值 queue队列 链表 递归 动态规划 排序和搜索 树 图

python数据结构和算法 参考 本文github 计算机科学是解决问题的研究。计算机科学使用抽象作为表示过程和数据的工具。抽象的数据类型允许程序员通过隐藏数据的细节来管理问题领域的复杂性。Python是一种强大但易于使用的面向对象语言。列表、元组和字符串都是用Python有序集合…

黑马毕向东Java课程笔记(day20-1——20-17)IO流:File类及相关方法、递归、递归的相关练习、Properties、PrintWriter类与PrintStream类、合并流与切割流

1、File类概述   File是文件和目录路径名的抽象表示形式。 用来将文件或者文件夹封装成对象&#xff0c;方便对文件与文件夹的属性信息进行操作。   前面说到的“流”&#xff0c;它只能操作数据&#xff0c;想要操作由数据封装成的文件的信息&#xff0c;必须使用File对象…

算法: 如何优雅的使用javascript递归画一棵结构树

递归和尾递归 简单的说&#xff0c;递归就是函数自己调用自己&#xff0c;它做为一种算法在程序设计语言中广泛应用。其核心思想是把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。一般来说&#xff0c;递归需要有边界条件、递归前进阶段和递归返回阶段…

day04函数名 闭包 迭代器 生成器(各种推导式和生成器表达式) 内置函数 装饰器 递归...

一.今日内容概 1. 函数本质就是一个内存地址. 用起来和变量一模一样 2. 闭包 内层函数对外层变量的使用 1. 保护变量 2. 让一个变量常驻内存 3. 迭代器 可迭代对象: __iter__ 迭代器: __iter__ __next__ from collecti…

linux 迭代列出文件,讲解在Linux命令行下使用ls命令列出文件的技巧

Linux ls 命令可以说是在 Linux 中常见到的命令之一&#xff0c;因为使用它可以掌握系统中文件所在目录中的内容&#xff0c;从而能够查看与修改文件&#xff0c;如果你正在使用 Linux ls 命令&#xff0c;不妨看一下以下技巧&#xff0c;它能帮助你更快速的完成任务。 ls 命令…

学习递归的另一种方法

每个学期&#xff0c;我都会通过一项调查&#xff0c;以获取有关我的教学的一些反馈。 上学期终于有人给我写一篇新文章的想法。 特别是&#xff0c;他们想了解有关递归的更多信息&#xff0c;所以我认为我会综合一些技巧。 递归概述 对于那些可能是第一次学习递归的人&#x…

用非递归方法实现递归算法时_学习递归的另一种方法

用非递归方法实现递归算法时 每个学期&#xff0c;我都会通过一项调查&#xff0c;以获取有关我的教学的一些反馈。 上学期终于有人给我写一篇新文章的想法。 特别是&#xff0c;他们想了解有关递归的更多信息&#xff0c;所以我认为我会综合一些技巧。 递归概述 对于那些可能…

bat 文件夹移动

echo off echo hello world ::得到当天 set pathLog%date:~0,4%%date:~5,2%%date:~8,2% :: eg:能得到 秒:set pathLog%date:~0,4%%date:~5,2%%date:~8,2%%time:~0,2%%time:~3,2%%time:~6,2% :: set pathLog%pathLog: 0% ::创建文件夹 md "d:\log\log%pathLog% ec…

【Python零基础入门篇 · 19】:os模块、可迭代对象和迭代器对象

目录 一、os模块 1、os模块中的命令&#xff1a; 2、常用命令的代码演示 os.getcwd() os.chdir(path) os.listdir(path) os.mkdir(path) os.makedirs(path) os.rename(旧名,新名) 3、举例&#xff1a;查找文件夹下所有满足要求的文件 二、可迭代对象和迭代器对象 1、…

day18:File(构造方法、创建、删除文件或者文件夹、 判断性、重命名与剪切、得到性、过滤性)、递归(遍历文件夹文件)

一 回顾 1.HashMap集合 特点: A.数据结构也是Hash表结构 B.多线程中是不安全 C.默认的数组的初始化容量是16 2.HashMap与HashSet的比较 相同点:都是hash表结构来存储 不同点: A.HashSet的底层就是使用HashMap来实现 B.HashSet的数据结构针对与是元素 HashMap的…

Python 文件和文件夹 01

Python文件和文件夹 01 ① 修改当前目录&#xff0c;首次利用 pandas 读取 excel 表 os.chdir import os import pandas as pd os.chdir(C:/aa/bb/cc) os.chdir(rC:\aa\bb\cc)数据 pd.read_excel("temp.xlsx") 等同于当前路径下的 temp.xlsx 文件。print(数据)② 字…

删除win10无限嵌套文件夹

解决由于失误操作导致WIN10系统产生无限循环的文件夹问题 昨天本想自己写一个拷贝文件的小程序&#xff0c;结果出现了点小问题&#xff0c;整出了一个无限循环的文件夹&#xff0c;直接删除出出现错误代码提示&#xff0c;显示无法删除&#xff0c;然后我就去网上找解决方案&…

计算机专硕一般研二在干嘛,专硕一般研二在干嘛,专硕两年怎么安排

一般学习两年。 硕士学位的学制取决于您申请的学校和专业。 不同的学校可能不同&#xff0c;同一所学校的硕士学位也可能不同。 许多学校还设有两年半的学制&#xff0c;甚至三年制的学制。本文一起来看一下吧~ 一.什么人适合读专硕 1、英语相对一般的人 学硕主要考英语一试卷&…

研二导师画大饼,不给时间实习,咋办

一个小学弟最近咨询我 我是本硕都在一所双非一本就读&#xff0c;软件工程&#xff0c;目前研二&#xff0c;23 届。我想在暑期进行一下今年的实习&#xff0c;想着可能对后面秋招和来年春招有帮助&#xff0c;但是实验室管的严导师不放时间(其实我当时是为了毕业条件和平时研…

2022年终总结与2023新年展望

前言 时间过得太快了&#xff0c;虽然写博客已经很多年了&#xff0c;但是年终总结一直由于种种原因没有写过&#xff0c;2022年确实是魔幻的一年&#xff0c;不知不觉自己也已经研二了&#xff0c;因为疫情的原因突然放开&#xff0c;提前放假回家&#xff0c;借此机会写一下…

研二(上学期)计划安排

今天是9.17号了&#xff0c;时间过得很快&#xff0c;学习的脚步永远停不下来。 时间的安排就不说了&#xff0c;真的是计划赶不上变化&#xff0c;一句话&#xff0c;除了外聘上课和研助&#xff0c;其他的时间必须到达实验室&#xff0c;&#xff08;一个星期一次总结&#…

计算机科学与探索、计算机工程与应用投稿经验分享

目录 等级&#xff1a; 经验&#xff1a; 总结&#xff1a; 等级&#xff1a; 计算机科学与探索 CCFb 计算机工程与应用 CCFB(2022年由c升为b) 经验&#xff1a; 首先本人主要关注计算机人工智能图像处理领域&#xff0c;北京某高校研二学生&#xff0c;水平不高。在研一…

计算机专业学生如何写一份优秀的校招简历(大三、研二学生请进)

计算机专业学生如何写一份优秀的校招简历(大三、研二学生请进) 最近讲了一节简历的公开课&#xff0c;还是蛮有价值的&#xff0c;想分享给大家 主要是讲解计算机相关专业的学生&#xff0c;就业找工作的简历&#xff0c;该如何书写。 内容包含&#xff1a; 1、快速掌握一份校…

快速傅里叶变换(研二的我终于弄懂了)

研二的我仍然对快速傅里叶变换一知半解&#xff0c;于是乎&#xff0c;本着待在家里&#xff0c;能耗时间就多耗点&#xff0c;不知道何年马月我才可以在外面快乐的奔跑~~ 快速傅里叶变换的实现&#xff08;c版本&#xff09; 在做项目的时候&#xff0c;需要用到matlab里的ff…

华电软工非全研究生学习工作总结-研二开学总结

昨晚加班太晚,就打算调休一天,养好精神,晚上开车回老家,开启假期模式。午休过后没啥事,随后就想着水一篇文章吧。 1、研究生学习 1.1、学生证来啦 今天对学生们来说,最大的喜事就是,期待了一学期的研究生学生证从北京邮寄了。看到微信群同学们开心的晒着学生证,我也期…