数组转树形结构

将扁平数组转换为嵌套树形结构的实现方法

问题

将带有 idparentId 的扁平数组转换为嵌套的树形结构。

// 输入
const list = [
  { id: 1, name: '部门A', parentId: 0 },
  { id: 2, name: '部门B', parentId: 1 },
  { id: 3, name: '部门C', parentId: 1 },
  { id: 4, name: '部门D', parentId: 2 },
  { id: 5, name: '部门E', parentId: 3 },
]

// 输出
[
  {
    id: 1,
    name: '部门A',
    parentId: 0,
    children: [
      { id: 2, name: '部门B', parentId: 1, children: [
        { id: 4, name: '部门D', parentId: 2, children: [] }
      ]},
      { id: 3, name: '部门C', parentId: 1, children: [
        { id: 5, name: '部门E', parentId: 3, children: [] }
      ]}
    ]
  }
]

解答

方法一:两次遍历(Map 映射)

function arrayToTree(list, rootId = 0) {
  // 第一次遍历:创建 id -> node 的映射
  const map = new Map()
  list.forEach(item => {
    map.set(item.id, { ...item, children: [] })
  })

  const result = []

  // 第二次遍历:建立父子关系
  list.forEach(item => {
    const node = map.get(item.id)
    if (item.parentId === rootId) {
      // 根节点直接放入结果
      result.push(node)
    } else {
      // 找到父节点,添加到其 children 中
      const parent = map.get(item.parentId)
      if (parent) {
        parent.children.push(node)
      }
    }
  })

  return result
}

方法二:一次遍历

function arrayToTree(list, rootId = 0) {
  const map = new Map()
  const result = []

  list.forEach(item => {
    // 获取或创建当前节点
    const node = map.get(item.id) || { children: [] }
    // 合并数据
    Object.assign(node, item)
    map.set(item.id, node)

    if (item.parentId === rootId) {
      result.push(node)
    } else {
      // 获取或创建父节点
      const parent = map.get(item.parentId) || { children: [] }
      map.set(item.parentId, parent)
      parent.children.push(node)
    }
  })

  return result
}

方法三:递归

function arrayToTree(list, parentId = 0) {
  return list
    .filter(item => item.parentId === parentId)
    .map(item => ({
      ...item,
      children: arrayToTree(list, item.id)
    }))
}

测试

const list = [
  { id: 1, name: '部门A', parentId: 0 },
  { id: 2, name: '部门B', parentId: 1 },
  { id: 3, name: '部门C', parentId: 1 },
  { id: 4, name: '部门D', parentId: 2 },
  { id: 5, name: '部门E', parentId: 3 },
]

console.log(JSON.stringify(arrayToTree(list), null, 2))

关键点

  • Map 映射:用 Map 存储 id 到节点的映射,实现 O(1) 查找
  • 时间复杂度:两次遍历和一次遍历都是 O(n),递归是 O(n²)
  • 处理顺序:一次遍历法不依赖数组顺序,父节点可以在子节点之后出现
  • 递归简洁但性能差:每次递归都要遍历整个数组,数据量大时不推荐
  • 注意引用关系:Map 存储的是对象引用,修改会同步到结果中