date: 2022-04-14
description: 针对项目中出现树形结构数据的时候,我们怎样去操作他
项目中我们会经常出现对树形结构的遍历、查找和转换的场景,比如说DOM树、族谱、社会机构、组织架构、权限、菜单、省市区、路由、标签等等。那针对这些场景和数据,我们又如何去遍历和操作,有什么方式或者技巧可以简化我们的实现思路。下面我们将针对常规出现的场景去总结一下我们的遍历方式
树的特点
1、每个节点都只有有限个子节点或无子节点;
2、没有父节点的节点称为根节点;
3、每一个非根节点有且只有一个父节点;
4、除了根节点外,每个子节点可以分为多个不相交的子树;
5、树里面没有环路
下面的图片表示一颗树
在下面的JS中我们由多棵树组成我们的数据
在这数据中我们如何评判数据是否为叶节点(也就是最后一级),我们每个节点都会存在children属性,如果不存在children属性或者children不是一个数组或者children为数组且长度为0我们则认为他是一个叶节点
我们针对树结构的操作离不开遍历,遍历的话又分为广度优先遍历、深度优先遍历。其中深度优先遍历可以通过递归和循环的方式实现,而广度优先遍历的话是非递归的
从上往下对每一层依次访问,在每一层中,从左往右(也可以从右往左)访问结点,访问完一层就进入下一层,直到没有结点可以访问为止。即访问树结构的第n+1层前必须先访问完第n层。
简单的说,BFS是从根节点开始,沿着树的宽度遍历树的节点。如果所有节点均被访问,则算法中止。
所以我们的实现思路是,维护一个队列,队列的初始值为树结构根节点组成的列表,重复执行以下步骤直到队列为空:
取出队列中的第一个元素,进行访问相关操作,然后将其后代元素(如果有)全部追加到队列最后。
深度优先搜索算法(英语:Depth-First-Search,DFS)是一种用于遍历或搜索树或图的算法。这个算法会尽可能深的搜索树的分支。当节点v的所在边都己被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止
1、先序遍历
访问子树的时候,先访问根再访问根的子树
2、后序遍历
访问子树的时候,先访问子树再访问根
1、先序遍历
先序遍历与广度优先循环实现类似,要维护一个队列,不同的是子节点不追加到队列最后,而是加到队列最前面
2、后序遍历
后序遍历就略微复杂一点,我们需要不断将子树扩展到根节点前面去,执行列表遍历,并且通过一个临时对象维护一个id列表,当遍历到某个节点如果它没有子节点或者它本身已经存在于我们的临时id列表,则执行访问操作,否则继续扩展子节点到当前节点前面
对于树结构的遍历操作,其实递归是最基础,也是最容易理解的。递归本身就是循环的思想,所以可以用循环来改写递归,以上的方式在项目中已经廊括了大部分的场景了,我们在日常开发中可以根据场景或者需要去选择我们的遍历方式,或者基于此对他进行调整和优化,至于每种方式的空间复杂度和时间复杂度我们在这个地方就不去尝试了,各位感兴趣可以自己去验证。
广度优先搜索
树的遍历
深度优先搜索
图文详解两种算法:深度优先遍历(DFS)和广度优先遍历(BFS)
二叉树遍历(前序,后序,中序,层次)递归与迭代实现JavaScript
JS树结构操作:查找、遍历、筛选、树和列表相互转换
首先定义一个数组
const arr = [1,2,3,4,5,6]
第一种:for循环
for (let i = 0i<arr.lengthi++){
console.log(arr[i])
}
for(j=0,len=arr.lengthj<lenj++){}//这种方法基本上是所有循环遍历方法中性能最高的一种
第二种 for of (需要ES6支持) 性能要好于forin,但仍然比不上普通for循环
for (let value of arr){
console.log(value)
}
第三种 for in 它的效率是最低的
for (let i in arr){
console.log(arr[i])
}
第四种 foreach() 实际上性能比普通for循环弱
1、箭头函数写法
arr.forEach(value =>{
console.log(value)
})
2、普通函数写法
arr.forEach(function(value){
console.log(value)
})
第五种 entries()
for (let [index, value] of arr.entries()) {
console.log(value)
}
第六种 keys()
for (let inx of arr.keys()){
console.log(arr[inx])
}
第七种 reduce()
1、箭头函数
arr.reduce((pre,cur)=>{
console.log(cur)
})
2、普通函数
arr.reduce(function(pre,cur){
console.log(cur)
})
第八种 map() 但实际效率还比不上foreach
1、箭头函数
arr.map(value=>{
console.log(value)
})
2、普通函数
arr.map(function(value){
console.log(value)
})
第九种 values()
for (let value of arr.values()){
console.log(value)
}
数组:
var arr = [‘a’,‘b’,'c']
for (let i = 0i<arr.lengthi++){
console.log(i,arr[i])
}
对象:
var person = { name:'tom',age:'29',sex:'男‘};
// 对象如果要用for循环来遍历,需要先有Object.key()的方法来拿到可迭代(遍历)的私有属性名的集合(数组)
var keys = Object.keys(person)
for (let i = 0i<keys.lengthi++){
console.log(keys[i]+":"+person[keys[i]])
}
数组:
for(let key in arr){
// 值得注意的是,key为数组的索引,如若需要获取属性的值,则需要使用数组加索引下标 的形式来获取
console.log(key,arr[key])
}
对象:
for(let key in obj){
// 值得注意的是,key为对象的键名,键值要以obj[key]的方式来获取
console.log(key,obj[key])
}
数组:
for(let value of arr){
//值得注意的是,for...of跟for...in是不一样的,for...in遍历的是数组的索引,for...of遍历的是数组的值
console.log(value)
}
对象:
for...in是不能单独来遍历一个对象的,会报错。
如果对象是一个类数组对象,那用array.from方法转成一个数组就可以用for...of来遍历了。
或者结合Object.keys()方法来使用
for(let key of Object.keys(obj)){
// for...of遍历输出的也是键名
console.log(key,obj[obj])
}
数组:
forEach()方法遍历数组是没有返回值
值得注意的是数组中有几项,那么传递进去的匿名回调函数就需要执行几次;
arr.forEach((item,index,arr)=>{
console.log(item,index,arr)
)
// 其中item是数组中的当前项,index是数组中的当前下标,arr是原始数组
对象:
原则上forEach是用来遍历数组的,不能遍历对象,但是可以用Object.getOnwPropertyNames()方法来使得对象能被forEach遍历出来。
Object.getOnwPropertyNames()返回一个数组,成员是参数对象自身全部属性的属性名,不管该属性是否能被遍历。
Object.getOnwPropertyNames(obj).forEach((item,index,arr)=>{
console.log(item,index,obj[item])
})