数据转换

170 阅读4分钟

如何在扁平数据结构和树结构中进行相互转化,下面是两种数据结构的基本格式。

扁平数据结构

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…