如何在扁平数据结构和树结构中进行相互转化,下面是两种数据结构的基本格式。
扁平数据结构
bianpingData:[
{
id:1,name:'根节点1',pid:null
},
{
id:2,name:'根节点2',pid:null
},
{
id:3,name:'子节点1',pid:1
},
{
id:4,name:'子节点2',pid:2
},
{
id:5,name:'孙节点1',pid:3
},
{
id:6,name:'孙节点2',pid:4
}
]
树结构数据
treeData:[
{
id:1,name:'根节点1',children:[
{
id:3,name:'子节点1',children:[
{
id:6,name:'孙节点1',children:[]
}
]
}
]
},
{
id:2,name:'根节点2',children:[
{
id:3,name:'子节点2',children:[
{
id:6,name:'孙节点2',children:[]
}
]
}
]
}
]
显而易见,我们可以通过id和pid这两个属性的是否匹配来确认父子关系,从而转化其数据结构
一、扁平数据结构转树
1.1 递归
扁平数据结构转化树,很容易想到递归,将树的结构拆分,其实就是一对对父子关系,所以所设置的递归函数,其实就是解决两个问题
-
父子节点匹配后如何重新定义父子节点
-
匹配的父子节点如何改变数据格式
以下是基本的实现过程
function arrayTotree(array) { // 第一步,找出所有根节点 const root = array.filter( //判断为根节点的条件 item => !item.pid ) // 第二步,找出所有子节点 const children = array.filter( //判断为子节点的条件 item => item.pid ) // 第三步,调用递归函数 translator(root,children) return root } // 递归函数 function translator (root,children) { root.forEach(item => { item.children = [] // 初始化children数组 children.forEach((current,index) => { // 若父子节点匹配 if(current.pid === item.id) { // 以匹配的子节点作为新的父节点再次调用递归进行查找 translator([current],children) // 将匹配的子节点添加进入父节点的children数组中 item.children.push(current) } }) }); } console.log(JSON.stringify(arrayTotree(bianpingData))) /* 输出结果为 [{"id":1,"name":"根节点1","pid":null,"children":[{"id":3,"name":"子节点1","pid":1,"children":[{"id":5,"name":"孙节点1","pid":3,"children":[]}]}]},{"id":2,"name":"根节点2","pid":null,"children":[{"id":4,"name":"子节点2","pid":2,"children":[{"id":6,"name":"孙节点2","pid":4,"children":[]}]}]}] */缺陷:
由于是嵌套多重循环,如果树状层级过多或者数据量过大,十分消耗时间,时间复杂度为
O(2^n),呈指数级增长。
1.2 使用map
map是一种字典数据结构,它采取键值对的数据格式存储数据,同时,它的键是不重复的,通过将数据以map的数据结构(因为键为字符串,这里使用对象来实现)来进行存储,然后通过键的匹配,可以轻松实现父子节点的关系匹配
具体代码如下:
// 使用map转成树
function arrayTotreeBymap(array) {
let tree = [] //最终结果集
const map = {}
//以map结构存储
array.forEach(item => {
map[item.id] = {...item, children: []}
})
console.log(map)
/*存储的map结构数据
{
'1': { id: 1, name: '根节点1', pid: null, children: [] },
'2': { id: 2, name: '根节点2', pid: null, children: [] },
'3': { id: 3, name: '子节点1', pid: 1, children: [] },
'4': { id: 4, name: '子节点2', pid: 2, children: [] },
'5': { id: 5, name: '孙节点1', pid: 3, children: [] },
'6': { id: 6, name: '孙节点2', pid: 4, children: [] }
}*/
array.forEach(item => {
if(map[item.pid]) //若有匹配数据,则为子节点,添加至相应的父节点childre数组
{
let parent = map[item.pid]
parent.children.push(item)
}else{
tree.push(item) //若无匹配数据,则为根节点,直接加入结果集中
}
})
console.log(map)
/* 处理后的map结构数据
{
'1': { id: 1, name: '根节点1', pid: null, children: [ [Object] ] },
'2': { id: 2, name: '根节点2', pid: null, children: [ [Object] ] },
'3': { id: 3, name: '子节点1', pid: 1, children: [ [Object] ] },
'4': { id: 4, name: '子节点2', pid: 2, children: [ [Object] ] },
'5': { id: 5, name: '孙节点1', pid: 3, children: [] },
'6': { id: 6, name: '孙节点2', pid: 4, children: [] }
}*/
return tree
}
console.log(JSON.stringify(arrayTotreeBymap(bianpingData)))
/* 输出结果为
[{"id":1,"name":"根节点1","pid":null,"children":[{"id":3,"name":"子节点1","pid":1,"children":[{"id":5,"name":"孙节点1","pid":3,"children":[]}]}]},{"id":2,"name":"根节点2","pid":null,"children":[{"id":4,"name":"子节点2","pid":2,"children":[{"id":6,"name":"孙节点2","pid":4,"children":[]}]}]}] */
使用map这种数据结构,只需要进行两次遍历就可以实现树的转换,时间复杂度为o(2n),但是需要耗费o(n)的空间
二、多维数组扁平化
如何将多维数组扁平化,其实也就是将一对对父子关系给拆分出来,所以这里我们还是采用递归的方式进行拆分
但需要注意的是,拆分完的父子关系,需要添加一个能互相匹配的条件(id和pid)以及需要删除掉多余的children数组属性
以下是具体的代码实现
const treeToArray = (tree) => {
const array = [] // 最终结果集
// 递归函数
//tree为要转换的树,id为子节点对应的pid
const translator = (tree,id) =>
{
tree.forEach(item => {
// 如果还有children,继续递归
if(item.children.length !== 0)
{
translator(item.children,item.id)
}
// 结束递归
// 第一步:删除children数组
delete item.children
// 第二步:添加pid属性,能使父子节点匹配
item.pid = id
// 第三步:存入结果集
array.push(item)
return
})
}
// 开始递归函数
translator(tree,null)
return array
}
console.log(treeToArray(treeData))
/* 输出结果如下
[
{ id: 6, name: '孙节点1', pid: 3 },
{ id: 3, name: '子节点1', pid: 1 },
{ id: 1, name: '根节点1', pid: null },
{ id: 5, name: '孙节点2', pid: 4 },
{ id: 4, name: '子节点2', pid: 2 },
{ id: 2, name: '根节点2', pid: null }
] */
参考文献: juejin.cn/post/698390…