优雅的使用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 相关文章推荐
MooTools 1.2中的Drag.Move来实现拖放
Sep 15 Javascript
jQuery AJAX回调函数this指向问题
Feb 08 Javascript
javascript解析json数据的3种方式
May 08 Javascript
JS判断浏览器是否安装flash插件的简单方法
Sep 13 Javascript
js创建对象几种方式的优缺点对比
Sep 28 Javascript
AngularJS实现DOM元素的显示与隐藏功能
Nov 22 Javascript
ES6新特性之字符串的扩展实例分析
Apr 01 Javascript
JavaScript基于activexobject连接远程数据库SQL Server 2014的方法
Jul 12 Javascript
Vue2.0学习系列之项目上线的方法步骤(图文)
Sep 25 Javascript
javascript用defineProperty实现简单的双向绑定方法
Apr 03 Javascript
JavaScrip如果基于url实现图片下载
Jul 03 Javascript
详细谈谈JavaScript中循环之间的差异
Aug 23 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中的生成XML文件的4种方法分享
2012/10/06 PHP
PHP单态模式简单用法示例
2016/11/16 PHP
PHP实现函数内修改外部变量值的方法示例
2018/12/28 PHP
Javascript 兼容firefox的一些问题
2009/05/21 Javascript
jQuery解决iframe高度自适应代码
2009/12/20 Javascript
JavaScript中也使用$美元符号来代替document.getElementById
2010/06/19 Javascript
JavaScript mapreduce工作原理简析
2012/11/25 Javascript
JS实现自适应高度表单文本框的方法
2015/02/25 Javascript
使用jQuery实现图片遮罩半透明坠落遮挡
2015/03/16 Javascript
javascript基于DOM实现权限选择实例分析
2015/05/14 Javascript
javascript时间差插件分享
2016/07/18 Javascript
用v-html解决Vue.js渲染中html标签不被解析的问题
2016/12/14 Javascript
canvas实现十二星座星空图
2017/02/14 Javascript
JS实现div模块的截图并下载功能
2017/10/17 Javascript
js遍历添加栏目类添加css 再点击其它删除css【推荐】
2018/06/12 Javascript
使用vue-infinite-scroll实现无限滚动效果
2018/06/22 Javascript
Vue中computed及watch区别实例解析
2020/08/01 Javascript
Vue 禁用浏览器的前进后退操作
2020/09/04 Javascript
python支持断点续传的多线程下载示例
2014/01/16 Python
将Python中的数据存储到系统本地的简单方法
2015/04/11 Python
Python 实现简单的电话本功能
2015/08/09 Python
Python实现的生成格雷码功能示例
2018/01/24 Python
Python cookbook(数据结构与算法)筛选及提取序列中元素的方法
2018/03/19 Python
python逐行读写txt文件的实例讲解
2018/04/03 Python
python实现各种插值法(数值分析)
2019/07/30 Python
Python lambda表达式filter、map、reduce函数用法解析
2019/09/11 Python
python实现树的深度优先遍历与广度优先遍历详解
2019/10/26 Python
使用Python制作缩放自如的圣诞老人(圣诞树)
2019/12/25 Python
Selenium及python实现滚动操作多种方法
2020/07/21 Python
详解Html5 Canvas画线有毛边解决方法
2018/03/01 HTML / CSS
印度尼西亚电子产品购物网站:Kliknklik
2018/06/05 全球购物
新娘父亲婚礼致辞
2014/01/16 职场文书
党员大会主持词
2014/04/02 职场文书
2014年法院个人工作总结
2014/12/17 职场文书
世界十大动漫制作公司排行榜,迪士尼上榜,第二是美国代表性文化符
2022/03/18 欧美动漫
搭建Yolov5服务器
2022/04/30 Servers