前言
多的不说,少的不唠,“扁平数据结构转tree”非常重要。我整理了一下这个算法的实现思路和实现过程。
咱们循序渐进,先不考虑性能,只实现功能,然后再慢慢优化!
另外,本期博客参与了【新星计划】,还请大家三连支持一下🌟🌟🌟感谢感谢💓💓💓
要实现的效果:
原始数据:
let arr = [
{id: 1, name: '部门1', pid: 0},
{id: 2, name: '部门2', pid: 1},
{id: 3, name: '部门3', pid: 1},
{id: 4, name: '部门4', pid: 3},
{id: 5, name: '部门5', pid: 4},
]
输出结果:
[
{
"id": 1,
"name": "部门1",
"pid": 0,
"children": [
{
"id": 2,
"name": "部门2",
"pid": 1,
"children": []
},
{
"id": 3,
"name": "部门3",
"pid": 1,
"children": [
// 结果 ,,,
]
}
]
}
]
嵌套遍历
思路:
1. 看下图,因为对象属于引用数据类型,引用类型数据在栈内存中保存的实际上是对象在堆内存中的引用地址。所以当你操作对象时,只能操作对象在栈内存中的引用地址。比如下图,当源对象发生改变,那么浅拷贝出来的数据也会随之改变;
2. 因为每个对象只有唯一的一个pid,也就是唯一的一个父级元素,所以我们只要嵌套两层遍历这个数组,就能将数组里的每个元素都找到自己的父元素,并插入到其chirden属性中。然后,因为对象的引用特性,就实现了扁平数据转成tree的效果。
function queryChilrden(arr) {
let result = []
arr.forEach(s => {
s.chilrden=[]
arr.forEach(d=>{
if(s.id==d.pid){
s.chilrden.push(d)
}
})
})
return arr.find(item => item.pid === 0)
}
递归实现
我想大部分人应该第一反应就是用递归实现了。
思路:
1. 首先我们从最外层开始遍历,第一级是pid等于0的元素;
2. 然后递归查找pid等于上一级id的元素
实现代码:
function foo(list) {
let result = []; // 结果集
getChildren(list, result, 0); // 开始递归查找子元素,初始传入pid为0
return result;
}
/**
* @param {*} data 源数组
* @param {*} result 将结果添加到这个数组
* @param {*} pid 父id
*/
function getChildren(data, result, pid) {
for (let item of data) { // 遍历传入的数组
if (item.pid === pid) { // 如果当前元素的pid等于传进来的父id,则
// 将当前对象所有属性和新增属性chilrden合并成一个新对象
let newItem = {...item, chilrden : []};
result.push(newItem); // 将新对象添加的传进来的数组中
// 将新对象的chilrden作为结果数组传入,下一层的结果则会添加到这一层对象的chilrden中
getChildren(data, newItem.chilrden, item.id);
}
}
}
优点:
实现简单,可读性好
缺点:
如果数据量过大,性能会比较差
利用map结构实现(性能最佳)
思路:
1. 注意这里说的map不是指es6的map数据结构,当然了,你用其实也可以。主要是要把元素的每一项改造成 { id: {...原本的对象,chirden:[] } }这种结构的。例如:
把
var arr = [{ id: 1, name: '部门1', pid: 0 },{ id: 2, name: '部门2', pid: 1 }]
改造成
{
1 : { id: 1, name: '部门1', pid: 0, chirden: [] },
2 : { id: 2, name: '部门2', pid: 1, chirden: [] }
}
2. 实现思路的话其实和第一种嵌套遍历差不多,也是利用了对象的引用。只不过我们利用这种结构的数据,可以减少一层遍历。
实现代码:
function mapFun(arr){
let mapItem = {}
let result = []
arr.forEach(item=>{
mapItem[item.id] ={...item,chilrden:[]}
})
arr.forEach(s=>{
if(s.pid === 0 ){
result.push(mapItem[s.id])
}else{
if(mapItem[s.pid]){
//用s.pid为key去mapItem里找,如果找到了。就说明当前这一项在mapItem里有父亲,然后就用父亲将当前s push进去。
mapItem[s.pid].chilrden.push(mapItem[s.id])
}
}
})
return result
}
最后
我觉得"扁平数据结构转Tree"这个题还是挺老生常谈的,而且平常工作中也偶尔会用到。面试也经常考。所以大家还是要抽时间琢磨明白的。其实最主要的是不能死记,是要明白它的逻辑,然后以后遇到类似的问题,也能触类旁通,这样才算是真正提升了自己!