优雅的使用javascript递归画一棵结构树示例代码


Posted in Javascript onSeptember 22, 2019

优雅的使用javascript递归画一棵结构树示例代码

递归和尾递归

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

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

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

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

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. 绘制分形图形

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

优雅的使用javascript递归画一棵结构树示例代码

优雅的使用javascript递归画一棵结构树示例代码

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

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)

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

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

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

效果图:

优雅的使用javascript递归画一棵结构树示例代码

优雅的使用javascript递归画一棵结构树示例代码

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

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为我们建的测试目录,如下:

优雅的使用javascript递归画一棵结构树示例代码

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

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
jquery 将disabled的元素置为enabled的三种方法
Jul 25 Javascript
js 内存释放问题
Apr 25 Javascript
Javascript下判断是否为闰年的Datetime包
Oct 26 Javascript
xss文件页面内容读取(解决)
Nov 28 Javascript
window.onresize 多次触发的解决方法
Nov 08 Javascript
使用typeof判断function是否存在于上下文
Aug 14 Javascript
IONIC自定义subheader的最佳解决方案
Sep 22 Javascript
vue-auto-focus: 控制自动聚焦行为的 vue 指令方法
Aug 25 Javascript
在Vue中使用icon 字体图标的方法
Jun 14 Javascript
微信小程序new Date()方法失效问题解决方法
Jul 29 Javascript
微信小程序引入VANT组件的方法步骤
Sep 19 Javascript
vue input标签通用指令校验的实现
Nov 05 Javascript
Webpack按需加载打包chunk命名的方法
Sep 22 #Javascript
jquery.tagsinput.js实现记录checkbox勾选的顺序
Sep 21 #jQuery
vue2.0+SVG实现音乐播放圆形进度条组件
Sep 21 #Javascript
js+springMVC 提交数组数据到后台的实例
Sep 21 #Javascript
jquery弹窗时禁止body滚动条滚动的例子
Sep 21 #jQuery
vue实现文件上传读取及下载功能
Nov 17 #Javascript
layer弹出框确定前验证:弹出消息框的方法(弹出两个layer)
Sep 21 #Javascript
You might like
用PHP实现WEB动态网页静态
2006/10/09 PHP
PHP错误处理函数
2016/04/03 PHP
PHPExcel中文帮助手册|PHPExcel使用方法(分享)
2017/06/09 PHP
php 输出缓冲 Output Control用法实例详解
2020/03/03 PHP
Javascript 加载和执行-性能提高篇
2012/12/28 Javascript
jquery实现背景墙聚光灯效果示例分享
2014/03/02 Javascript
详解AngularJS实现表单验证
2015/12/10 Javascript
dul无法加载bootstrap实现unload table/user恢复
2016/09/29 Javascript
AngularJS中指令的四种基本形式实例分析
2016/11/22 Javascript
JavaScript原生数组Array常用方法
2017/04/06 Javascript
JavaScript实现简单图片轮播效果
2017/08/21 Javascript
D3.js实现拓扑图的示例代码
2018/06/30 Javascript
Jquery实现获取子元素的方法分析
2019/08/24 jQuery
Layui数据表格跳转到指定页的实现方法
2019/09/05 Javascript
react-native聊天室|RN版聊天App仿微信实例|RN仿微信界面
2019/11/12 Javascript
JS如何在不同平台实现多语言方式
2020/07/16 Javascript
vue.js 输入框输入值自动过滤特殊字符替换中问标点操作
2020/08/31 Javascript
Python标准库shutil用法实例详解
2018/08/13 Python
Python随机函数库random的使用方法详解
2019/08/21 Python
对python中的*args与**kwgs的含义与作用详解
2019/08/28 Python
PyTorch 解决Dataset和Dataloader遇到的问题
2020/01/08 Python
python3 deque 双向队列创建与使用方法分析
2020/03/24 Python
Python实现小黑屋游戏的完整实例
2021/01/06 Python
苹果台湾官网:Apple台湾
2019/01/05 全球购物
IFCHIC台湾:欧美国际设计师品牌
2019/05/18 全球购物
阿迪达斯印尼官方网站:adidas印尼
2020/02/10 全球购物
统计每一学生的平均成绩
2014/06/06 面试题
在C#中如何实现多态
2014/07/02 面试题
给排水专业应届生求职信
2013/10/12 职场文书
爽歪歪广告词
2014/03/20 职场文书
学校关爱留守儿童活动方案
2014/08/27 职场文书
竞选学习委员演讲稿
2014/09/01 职场文书
小学班主任教育随笔
2015/08/15 职场文书
golang import自定义包方式
2021/04/29 Golang
浅谈JS和Nodejs中的事件驱动
2021/05/05 NodeJs
Python激活Anaconda环境变量的详细步骤
2021/06/08 Python