实现一个复杂度最低的,效率最高的 listToTree 方法

废话少说,直接上代码。

js 复制代码
const data = [
  {id: 1, parentId: null, name: "Parent Node 1"},
  {id: 5, parentId: 2, name: "Child Node 2.1"},
  {id: 6, parentId: 2, name: "Child Node 2.2"},
  {id: 2, parentId: null, name: "Parent Node 2"},
  {id: 3, parentId: 1, name: "Child Node 1.1"},
  {id: 4, parentId: 1, name: "Child Node 1.2"},
  {id: 7, parentId: 3, name: "Grandchild Node 1.1.1"}
];


function listToTree(data) {
  const map = {};

  data.forEach(item => {
    const node = {...item, children: []}
    if (!map[item.id]) {
      map[item.id] = node.children
    }

    const itemParentId = item.parentId || 0
    if (!map[itemParentId]) {
      map[itemParentId] = [node]
    } else {
      map[itemParentId].push(node)
    }
    node.children = map[item.id]
  })


  return map[0] || []
}

时间复杂度:O(n)

空间复杂度:O(n)

接下来说一说为什么说它是复杂度最低,效率最高的方法。

先看看常规 listToTree 实现方法。

1.递归

js 复制代码
function listToTree(data) {
    function loop(key) {
        const arr = [];
        data.forEach((item) = > {
            if (item.parentId === key) {
                const newItem = {...item, children: loop(item.id)
                }; // 深拷贝并递归
                arr.push(newItem);
            }
        });
        return arr;
    }
    return loop(null); // 从根节点开始 (parentId 为 null)
}

时间复杂度:O(n^2)

  • 每次调用 loop 函数时,都会遍历整个 data 数组,导致在最坏情况下(树非常深或宽)时间复杂度为 O(n^2),其中 n 是节点的数量。

空间复杂度:O(n)

  • 由于递归调用会使用栈来存储每次调用的状态,最坏情况下(树的深度为 n),空间复杂度为 O(n)。此外,返回的树结构也需要 O(n) 的空间。

2. 实现2

js 复制代码
function listToTree(data) {
  // 先生成一个以 id 为键的对象
  const obj = {};
  data.forEach((item) => {
    obj[item.id] = { ...item, children: [] }; // 确保每个节点都有 children 属性
  });

  const parentList = [];
  data.forEach((item) => {
    const parent = obj[item.parentId];
    if (parent) {
      // 当前项有父节点
      parent.children.push(obj[item.id]); // 将当前节点添加到其父节点的 children 中
    } else {
      // 当前项没有父节点 -> 顶层
      parentList.push(obj[item.id]); // 添加到顶层节点列表
    }
  });

  return parentList;
}

// 示例数据
const data = [
  { id: 1, parentId: null, name: "Parent Node 1" },
  { id: 5, parentId: 2, name: "Child Node 2.1" },
  { id: 6, parentId: 2, name: "Child Node 2.2" },
  { id: 2, parentId: null, name: "Parent Node 2" },
  { id: 3, parentId: 1, name: "Child Node 1.1" },
  { id: 4, parentId: 1, name: "Child Node 1.2" },
  { id: 7, parentId: 3, name: "Grandchild Node 1.1.1" }
];

// 使用函数
const tree = listToTree(data);
console.log(JSON.stringify(tree, null, 2));

时间复杂度:O(n)

空间复杂度:O(n)

但是显而易见,这个实现有2次遍历。

结语

这个实现方法我之前在掘金上看到的,但是现在已经找不到那篇文章了,也记不起原本是如何实现的。今天自己试着实现了一下,也是糊里糊涂实现的,主要是利用对象的引用特性实现。

这已经是我能想到最简单的实现了,如果有更好的实现,可以发出来看一看

相关推荐
JieE21218 分钟前
LeetCode 56. 合并区间|超清晰 JS 图解思路,面试高频区间题
javascript·算法·面试
candyTong3 小时前
RTK 技术原理:一次典型会话里,80% 上下文是怎么省下来的
javascript·后端·架构
_柳青杨7 小时前
深入理解 JavaScript 事件循环
前端·javascript
大家的林语冰13 小时前
ES5 凉凉,Babel 8 正式发布,默认不再编译为 ES5 和 CJS......
前端·javascript·前端工程化
weedsfly15 小时前
异步编程全景与事件循环——彻底搞懂 JS 执行机制
前端·javascript
用户17335980753715 小时前
纯前端 PDF 数字签名实战:Vue 3 + pdf-lib 在浏览器里完成签名嵌入
前端·javascript
JieE2121 天前
LeetCode 226. 翻转二叉树|JS 递归超详细拆解,二叉树入门经典题
javascript·算法
JieE2121 天前
LeetCode 104. 二叉树的最大深度|递归思路超详细拆解
javascript·算法
kyriewen1 天前
我用 AI 一周写完了整个项目,上线第一天就崩了——这是我踩过最贵的 5 个坑
前端·javascript·ai编程
Larcher1 天前
AI Loop:让AI像人一样自主完成任务的核心机制
javascript·人工智能·设计模式