优化实战 第 08 期 - 遍历一次把扁平数据结构转Tree

2,917 阅读2分钟

我正在参加「掘金·启航计划」

树结构(Tree) 是一种典型的非线性数据结构,它是由 n (n > 0) 个有限节点组成的一个具有层次关系的集合

tree.jpg

在实际的工作中,为了满足业务需求,需要把后端返回的扁平化的数组结构,转换成树形结构

数据结构

  • 扁平数据
     [
       { id: 1, pid: null, name: 'M1部门' },
       { id: 11, pid: 1, name: '张三' },
       { id: 12, pid: 1, name: '李四' },
       { id: 13, pid: 1, name: '王五' },
       { id: 2, pid: null, name: 'M2部门' },
       { id: 21, pid: 2, name: '赵六' },
       { id: 22, pid: 2, name: '周七' },
       { id: 23, pid: 2, name: '吴八' }
     ]
    
  • 树形数据
     [
       {
         id: 1, pid: null, name: 'M1部门',
         children: [
           { id: 11, pid: 1, name: '张三' },
           { id: 12, pid: 1, name: '李四' },
           { id: 13, pid: 1, name: '王五' }
         ]
       },
       {
         id: 2, pid: null, name: 'M2部门',
         children: [
           { id: 21, pid: 2, name: '赵六' },
           { id: 22, pid: 2, name: '周七' },
           { id: 23, pid: 2, name: '吴八' }
         ]
       }
     ]
    

递归算法

  • 定义

    程序调用自身的编程技巧称为递归

  • 作用

    它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量

  • 图解阶乘

    recursion.jpeg

    递归需要有边界条件、递归前进段和递归返回段

    当边界条件不满足时,递归前进;当边界条件满足时,递归返回

递归转换

  • 根据 pid 和 id 的对应关系筛选出根结点

    list.filter(item => item.pid === null)
    
  • 遍历根节点调用自身匹配子节点

    const listToTree = (list, {rootid = null, id = 'id', pid = 'pid'} = {}) => {
      return list.filter(item => item[pid] === rootid)
                  .map(item => ({ ...item, children: listToTree(list, { rootid: item[id] }) }))
    }
    
  • 性能解析

    递归容易造成堆栈的溢出,且消耗大量的内存

遍历转换

  • 核心思想

    利用引用数据类型浅拷贝的特性,直接从 Map 中找对应的数据进行存储

  • 通过 id 给列表的每一项做一个映射

    const hash = new Map()
    list.forEach(item => hash.set(item.id, item))
    
  • 通过 pid 从映射中取父节点

    const listToTree = (list = [], {id = 'id', pid = 'pid', branch = 'children'} = {}) => {
      const hash = new Map(), roots = []
      list.forEach(item => {
        hash.set(item[id], item)
        const parent = hash.get(item[pid])
        if (!parent) {
          roots.push(item)
        } else {
          !parent[branch] && (parent[branch] = [])
          parent[branch].push(item)
        }
      })
      return roots
    }
    

    如果节点不存在则当前节点 item 为根节点

    如果存在则把当前 item 节点添加到 parent 节点的 children 属性中

  • 参数注解

    /**
    * 扁平数据结构转Tree
    * 
    * @param {Array} list 源数据
    * @param {String} id 唯一的自增ID名称
    * @param {String} pid 父ID名称
    * @param {String} branch 树杈字段名称
    * @return {Array} roots 目标数据
    * @example
    *
    *   listToTree(data)
    *   listToTree(data, { branch: 'children' })
    */
    
  • 关联优化

    第33期 - 使用 while 循环把树结构拍平

  • 一起交流学习

    加群交流看沸点