当前位置: 首页 > news >正文

【前端手撕】数组转树

把平铺的数组结构转换为树结构。

const arr = [ { id: "01", name: "张大大", pid: "", job: "项目经理" }, { id: "02", name: "小亮", pid: "01", job: "产品leader" }, { id: "03", name: "小美", pid: "01", job: "UIleader" }, { id: "04", name: "老马", pid: "01", job: "技术leader" }, { id: "05", name: "老王", pid: "01", job: "测试leader" }, { id: "06", name: "老李", pid: "01", job: "运维leader" }, { id: "07", name: "小丽", pid: "02", job: "产品经理" }, { id: "08", name: "大光", pid: "02", job: "产品经理" }, { id: "09", name: "小高", pid: "03", job: "UI设计师" }, { id: "10", name: "小刘", pid: "04", job: "前端工程师" }, { id: "11", name: "小华", pid: "04", job: "后端工程师" }, { id: "12", name: "小李", pid: "04", job: "后端工程师" }, { id: "13", name: "小赵", pid: "05", job: "测试工程师" }, { id: "14", name: "小强", pid: "05", job: "测试工程师" }, { id: "15", name: "小涛", pid: "06", job: "运维工程师" }, ];

转换为:

[ { id: "01", name: "张大大", pid: "", job: "项目经理", children: [ { id: "02", name: "小亮", pid: "01", job: "产品leader", children: [ { id: "07", name: "小丽", pid: "02", job: "产品经理", children: [] }, { id: "08", name: "大光", pid: "02", job: "产品经理", children: [] } ] }, // ... 其他子节点 ] } ]

暴力递归

function toTree(list, parId = '') { let len = list.length function loop(parId) { let res = [] // 存当前层的所有子节点 // 递归遍历整个数组,找到当前层所有子节点(注意:每次都是遍历全部) for (let i = 0; i < len; i++) { if (list[i].pid === parId) { // 如果当前项的父id是parId,说明是当前层的子节点 // 递归调用loop函数,找到当前项的所有子节点 list[i].children = loop(list[i].id) // 把当前节点(带着它的children)放入结果 res.push(list[i]) } } return res } return loop(parId) }

因为每次递归都是遍历整个数组,所以时间复杂度是O(n²),数据量大的时候会很慢。

遍历数组其实是为了找到父节点对应的子节点,并进行组装。因此可以先用map来做一次映射,方便父子节点的快速定位和组装。优化为用哈希来解:

哈希解法

function toTreeHash(list) { const res = [] const map = {} // 先把所有项都放到哈希表中,浅拷贝不污染原数组 arr.forEach(item => map[item.id] = {...item, children:[]}) // 遍历数组,根据父id找到子节点,把子节点放到父节点的children数组中 for (let item of list) { let node = map[item.id] if (item.pid) { // 避免父节点不存在的情况导致报错 if (!map[item.pid]) continue // 把当前项放到父节点的children数组中 map[item.pid].children.push(node) } else { // 没有父id,说明是根节点 res.push(node) } } return res }

因为只遍历两次数组,时间复杂度为O(n)

http://www.cnnetsun.cn/news/2978710.html

相关文章:

  • Gemini模型国内合规使用指南与替代方案
  • Ultimate ASI Loader终极指南:3分钟掌握游戏MOD加载神器
  • 3分钟掌握音乐文件解密:解锁加密音频的完整解决方案
  • NXP ARM9微控制器选型与实战:经典架构在嵌入式开发中的现代价值
  • Node.js生产部署必须用Nginx反向代理的底层原因
  • Gemini 3.1 Pro实战指南:AI办公提效2.5小时的四类标准化流水线
  • GESP7级C++考试语法知识(四、哈希表(6、快速判断是否存在)
  • 音频驱动数字人详细步骤:2026矩阵口播工作流,5款选型实测
  • 调查研究-186 LangChain 和 LangGraph 的区别:从快速构建 Agent 到生产级工作流编排
  • Obsidian笔记如何优雅迁移到其他平台?3个技巧让知识流动起来
  • Debian 8 安装 JDK 8 的完整配置原理与实践
  • RaTA-Tool:基于检索增强的多模态大模型工具调用框架解析
  • OpenClaw本地智能体部署实战:从环境搭建到可调试工作流
  • 终极指南:5分钟在Mac上打造桌面歌词神器LyricsX
  • 英雄联盟玩家必备的LCU工具箱:3分钟掌握游戏效率提升的完整指南
  • 5分钟制作专业LRC歌词:零门槛的免费歌词制作工具完全指南
  • i.MX RT1160接口时序与引脚配置实战指南
  • 帆软安全深度解析:从SQL注入到业务逻辑漏洞的攻防实战
  • FXAS21002C陀螺仪SPI通信与寄存器配置实战指南
  • Debian 9 安装 Node.js 实战指南:nvm 方案详解
  • 嵌入式Flash完整性校验:基于NXP Flexis AC硬件CRC-CCITT模块的实战指南
  • Win11本地跑Hermes Agent:微信直连轻量级AI智能体网关
  • Linux rt_mutex实时互斥锁优先级继承与pi链
  • 长素材怎么随机混剪成新视频?5款长视频拆分深度对比
  • i.MX 6SoloX硬件设计:引脚分配、电源规划与PCB布局实战指南
  • 嵌入式接口时序设计:从核心概念到i.MX 7ULP实战解析
  • 【信息科学与工程学】【通信工程】CDN 系统组网和安全设计
  • Qwen 3.6本地部署实战:解决embedding异常、VLLM兼容与阿里云Docker陷阱
  • 嵌入式音频输出实战:双缓冲DMA与PWM/DAC/I2S方案详解
  • PROXIMA框架:如何科学评估代理指标,提升A/B测试决策效率与可靠性