优雅的使用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 相关文章推荐
让iframe框架网页在任何浏览器下自动伸缩
Aug 18 Javascript
浅析C/C++,Java,PHP,JavaScript,Json数组、对象赋值时最后一个元素后面是否可以带逗号
Mar 22 Javascript
Javascript闭包与函数柯里化浅析
Jun 22 Javascript
JS获取一个未知DIV高度的方法
Aug 09 Javascript
搭建Bootstrap离线文档的方法
Dec 02 Javascript
Vue.js如何实现路由懒加载浅析
Aug 14 Javascript
浅谈react性能优化的方法
Sep 05 Javascript
JS判断用户用的哪个浏览器实例详解
Oct 09 Javascript
解决layui-open关闭自身窗口的问题
Sep 10 Javascript
Vue项目中如何使用Axios封装http请求详解
Oct 23 Javascript
VSCode搭建React Native环境
May 07 Javascript
three.js显示中文字体与tween应用详析
Jan 04 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
打造计数器DIY三步曲(下)
2006/10/09 PHP
php中限制ip段访问、禁止ip提交表单的代码分享
2014/08/22 PHP
PHP7 新特性详细介绍
2016/09/06 PHP
Laravel学习基础之migrate的使用教程
2017/10/11 PHP
清除网页历史记录,屏蔽后退按钮!
2008/12/22 Javascript
IE9+已经不对document.createElement向下兼容的解决方法
2015/09/14 Javascript
jQuery Easyui实现左右布局
2016/01/26 Javascript
Bootstrap对话框使用实例讲解
2016/09/24 Javascript
JS实现的样式切换功能tableCSS实例
2016/12/30 Javascript
jQuery实现滚动条滚动到子元素位置(方便定位)
2017/01/08 Javascript
jQuery中绑定事件bind() on() live() one()的异同
2017/02/23 Javascript
AngularJS路由删除#符号解决的办法
2017/09/28 Javascript
vue项目启动出现cannot GET /服务错误的解决方法
2020/04/26 Javascript
Vue 中如何将函数作为 props 传递给组件的实现代码
2020/05/12 Javascript
js实现轮播图特效
2020/05/28 Javascript
前端vue如何使用高德地图
2020/11/05 Javascript
[01:51]2014DOTA2国际邀请赛 这个赛场没有失败者VGTi5再见
2014/07/23 DOTA
python调用windows api锁定计算机示例
2014/04/17 Python
Python与Redis的连接教程
2015/04/22 Python
Python求导数的方法
2015/05/09 Python
Python实现解析Bit Torrent种子文件内容的方法
2017/08/29 Python
selenium python浏览器多窗口处理代码示例
2018/01/15 Python
用 Python 连接 MySQL 的几种方式详解
2018/04/04 Python
Python一行代码解决矩阵旋转的问题
2019/11/30 Python
Python decorator拦截器代码实例解析
2020/04/04 Python
Python函数必须先定义,后调用说明(函数调用函数例外)
2020/06/02 Python
英国精品买手店:Browns Fashion
2016/09/29 全球购物
美国在线乐器和设备商店:Musician’s Friend
2018/07/06 全球购物
Shopee新加坡:东南亚与台湾电商平台
2019/01/25 全球购物
高中教师考核方案
2014/05/18 职场文书
公司欠款证明
2015/06/24 职场文书
导游词之平津战役纪念馆
2019/11/04 职场文书
springBoot基于webSocket实现扫码登录
2021/06/22 Java/Android
使用CSS实现一个搜索引擎的原理解析
2021/09/25 HTML / CSS
8个JS的reduce使用实例和reduce操作方式
2021/10/05 Javascript
python和anaconda的区别
2022/05/06 Python