优雅的使用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 相关文章推荐
Javascript调用XML制作连动下拉列表框
Jun 25 Javascript
取得父标签
Nov 14 Javascript
JQuery Study Notes 学习笔记(一)
Aug 04 Javascript
Jquery中children与find之间的区别详细解析
Nov 29 Javascript
node.js中的fs.symlink方法使用说明
Dec 15 Javascript
jQuery动画效果相关方法实例分析
Dec 31 Javascript
jQuery实现百叶窗焦点图动画效果代码分享(附源码下载)
Mar 14 Javascript
jquery编写日期选择器
Mar 16 Javascript
jQuery实现table中两列CheckBox只能选中一个的示例
Sep 22 jQuery
Taro集成Redux快速上手的方法示例
Jun 21 Javascript
vuejs 动态添加input框的实例讲解
Aug 24 Javascript
vue elementUI tree树形控件获取父节点ID的实例
Sep 12 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创建多级目录代码
2008/06/05 PHP
解析php中const与define的应用区别
2013/06/18 PHP
php多用户读写文件冲突的解决办法
2013/11/06 PHP
PHP快速排序quicksort实例详解
2016/09/28 PHP
微信 开发生成带参数的二维码的实例
2016/11/23 PHP
如何通过Apache在本地配置多个虚拟主机
2020/07/29 PHP
javascript判断ie浏览器6/7版本加载不同样式表的实现代码
2011/12/26 Javascript
javascript中onmouse事件在div中失效问题的解决方法
2012/01/09 Javascript
jquery 如何动态添加、删除class样式方法介绍
2012/11/07 Javascript
JS实现浏览器状态栏显示时间的方法
2015/10/27 Javascript
BootStrap中
2016/12/10 Javascript
JS实现最简单的冒泡排序算法
2017/02/15 Javascript
利用JS动态生成隔行换色HTML表格的两种方法
2018/10/09 Javascript
微信小程序 授权登录详解(附完整源码)
2019/08/23 Javascript
谈谈我在vue-cli3中用预渲染遇到的坑
2020/04/22 Javascript
详解JavaScript 作用域
2020/07/14 Javascript
Openlayers实现图形绘制
2020/09/28 Javascript
Python 字符串转换为整形和浮点类型的方法
2018/07/17 Python
pytorch: tensor类型的构建与相互转换实例
2018/07/26 Python
不归路系列:Python入门之旅-一定要注意缩进!!!(推荐)
2019/04/16 Python
python and or用法详解
2019/06/26 Python
Python利用requests模块下载图片实例代码
2019/08/12 Python
Python GUI学习之登录系统界面篇
2019/08/21 Python
Python谱减法语音降噪实例
2019/12/18 Python
Tensorflow实现将标签变为one-hot形式
2020/05/22 Python
matplotlib.pyplot.matshow 矩阵可视化实例
2020/06/16 Python
详解HTML5 canvas绘图基本使用方法
2018/01/29 HTML / CSS
详解三种方式实现平滑滚动页面到顶部的功能
2019/04/23 HTML / CSS
英国知名的护肤彩妆与时尚配饰大型综合零售电商:Unineed
2016/11/21 全球购物
受外贸欢迎的美国主机:BlueHost
2017/05/16 全球购物
土木工程师岗位职责
2013/11/24 职场文书
旷课检讨书1000字
2014/02/14 职场文书
岗位说明书范文
2014/05/07 职场文书
教师师德承诺书2016
2016/03/25 职场文书
网络新闻该怎么写?这些写作技巧你都知道吗?
2019/08/26 职场文书
阿里云服务器部署RabbitMQ集群的详细教程
2022/06/01 Servers