前言
树形结构存储数据是很方便的,里面有什么数据能够一目了然,但是在处理大量数据时,如果使用树形结构,增删改查所需要的时间复杂度会较高,让我们来看个例子
树形结构
以下是一个简单的树形结构数据
const tree = [
{ id:'01',value: 1, children: [
{ id:'01-1',value: 2, children: [
{ id:'01-1-1',value: 3},
{ id:'01-1-2',value: 4}
] },
{ id:'01-2',value: 5}
]},
{ id:'02',value: 6}
];
如果需要查找,最开始想到的算法就是递归
// 查找节点
const findValue=4 // 需要查找的节点值
const findNode=(tree)=>{
for(let node of tree){
if(node.value === findValue){ // 找到节点后返回
return node
}
else if(node.children && findNode(node.children)){ // 如果有 children,就递归查找子树的节点
return findNode(node.children)
}
}
}
const getNode=findNode(tree)
console.log(JSON.stringify(getNode)); // {"id":"01-1-2","value":4}
当 findValue 为根节点时为最好情况,时间复杂度是 O(1),如果 findValue 在树的最深叶子节点上,或者不存在,那么时间复杂度是 O(n),当树的深度较大时,查找所需要的时间将更多,维护树形结构的成本也更大
有什么其他方法可以降低这个复杂度呢,这就需要将树形结构扁平化
扁平化
先让我们来看扁平化后的数组
const flatArray=[
{id:'01',value:1}, // 没有父节点 就将 parentId 设为 undefined 或不写
{id:'01-1',value:2,parentId:'01'},
{id:'01-1-1',value:3,parentId:'01-1'},
{id:'01-1-2',value:4,parentId:'01-1'},
{id:'01-2',value:5,parentId:'01'},
{id:'02',value:6},
]
从上面扁平化后的数组可以看出,我们增加了一个属性 parentId,其值为父节点的 id,通过该属性与父节点保持连接,不会破坏原始的数据结构,这个时候要查找节点就方便多了
const findValue=4
const findNode=(arr)=>{
for(let item of arr){ // 直接遍历就能找到
if(item.value===findValue) return item
}
}
const getNode=findNode(flatArray)
console.log(JSON.stringify(getNode)); // {"id":"01-1-2","value":4,"parentId":"01-1"}
怎么转化
怎么将树形结构转化为扁平化的数组,来看简单的示例
const flat=(tree,parentId,res=[])=>{ // 初始 res 为空数组
for(let node of tree){
res.push({
id:node.id,
parentId:parentId,
value:node.value
})
if(node.children){ // 有子节点就递归遍历,传入的 parentId 参数为父节点 id, 也就是当前 node 的 id
flat(node.children,node.id,res)
}
}
return res
}
let flatArray=flat(tree,undefined)
for(let item of flatArray){
console.log(JSON.stringify(item)); // 输出每个节点
}
输出的结果为:
这样就能转化成功了
也可以使用 reduce 方法,reduce 作为累加器,可以实现同样的效果
const flat = (tree, parentId) => {
return tree.reduce((res,{id,value,children}) => { // 传入的参数为 上次积累值 res,和当前项解构
res.push({id,parentId,value})
if(children){
res=[...res,...flat(children,id)] // 利用扩展运算符 展开后合并
}
return res;
}, []) // 初始值为空数组
}
let flatArray=flat(tree,undefined)
for(let item of flatArray){
console.log(JSON.stringify(item)); // 结果同上图
}
reduce 方法接收一个回调函数和累加器初始值,回调函数可以有4个参数:上一次调用回调返回的值、当前被处理的元素、当前元素在数组中的索引、调用 reduce 的数组,在这里我们用到的是前两个参数,传入上次回调返回的 res,以及当前元素解构出来的值
res 把当前元素存入后,如果当前元素有子节点时,遍历子节点,同时也要和 res 合并,最终的运算结果和上个方法一致
比较
与树形结构相比,扁平化后的数组:
- 能够简化数据处理,因为线性结构通常更便于操作
- 添加或删除数据时,也更加方便
- 可以提高查询和搜索的性能