测试数据:
const data = [
{ id: 1, pid: 0 },
{ id: 2, pid: 1 },
{ id: 3, pid: 1 },
{ id: 4, pid: 3 },
{ id: 5, pid: 4 },
]
// 转换为
{
id: 1,
pid: 0,
children:[
{
id: 2,
pid: 1,
children:[]
},
{
id: 3,
pid: 1,
children:[
{
id: 4,
pid: 3,
children:[
{
id: 5,
pid: 4,
children:[]
}
]
}
]
}
]
}
思路:首先找到父级元素,然后遍历数组往父元素的children中push数据,通过递归向下一层数据中继续添加。
// 1.先遍历获取最外层的父元素
const parentArr = data.filter(item => !item.pid);
// 2.递归生成树结构
const toTree = (parent, child) => {
parent.forEach(item => {
// 添加children属性
item.children = [];
let list = [];
child.forEach(i => {
if(i.pid && i.pid===item.id) {
item.children.push(i);
list.push(i);
}
})
toTree(list, child);
})
return parent
}
toTree(parentArr, data);
然而,递归算法虽然简洁,但是效率却很低O(2^n),通常不作为推荐算法。
补充:
如何衡量算法的效率呢:一般从算法时间复杂度和空间复杂度来衡量。
时间复杂度:算法执行语句的次数。
计算方法:
找出算法中的基本语句:算法中执行次数最多的那条语句就是基本语句,通常是最内层循环的循环体
计算基本语句的执行次数的数量级:只需保留f(n)中的最高次幂正确即可,可以忽略所有低次幂和最高次幂的系数
如果算法中包含嵌套的循环,则基本语句通常是最内层的循环体,如果算法中包含并列的循环,则将并列循环的时间复杂度相加, 例如:
for(i=1; i<n; i++) x++;
for(i=1, i<n; i++){
for(j=1; j<n; j++) x++;
}
第一个for循环的时间复杂度为Ο(n),第二个for循环的时间复杂度为Ο(n²),则整个算法的时间复杂度为Ο(n+n²)=Ο(n²)。
注、加法原则:T(n)=O(f(n))+O(g(n))=O(max(fn,gn))
常见的算法时间复杂度由小到大依次为:Ο(1)<Ο(log2n)<Ο(n)<Ο(nlog2n)<Ο(n²)<Ο(n³)<…<Ο(2^n)<Ο(n!)<O(n^n)
空间复杂度:对一个算法在运行过程中临时占用存储空间的大小
计算方法:
忽略常数,用O(1)表示
递归算法的空间复杂度=(递归深度n)*(每次递归所要的辅助空间)
非递归来, 使用Map结构实现。
const toTree = (list = [], { rootid = 0 } = {}) => {
const parentMap = new Map();
const topNodes = [];
list.forEach(item => {
// list数据不是递增顺序,先将parentMap赋值,防止后面取不到
parentMap.set(item.id, item);
})
list.forEach(item => {
if(parentMap.has(item.id)) {
item.children = parentMap.get(item.id).children || [];
} else {
item.children = [];
}
if(parentMap.has(item.pid)) {
( parentMap.get(item.pid).children ||[]).push(item);
} else {
parentMap.set(item.pid,{children:[item]})
}
if(item.pid === robotId){
topNodes.push(item)
}
})
return topNodes;
}
toTree(data, { rootid: 0 })