JavaScript 将扁平的数组输出转为树形结构(需要考虑性能)

2023-11-12

 扁平数组转为树形结构,做后台管理系统时也是经常用到的功能;面试时也是常常出现的,今天实现一下,引用两篇掘金大佬的文章,感谢大佬

 一、什么是好算法?什么是坏算法?

        判断一个算法的好坏,一般从执行时间占用空间来看,执行时间越短,占用的内存空间越小,那么它就是好的算法。对应的,我们常常用时间复杂度代表执行时间,空间复杂度代表占用的内存空间。

时间复杂度

时间复杂度的计算并不是计算程序具体运行的时间,而是算法执行语句的次数。

 随着n的不断增大,时间复杂度不断增大,算法花费时间越多。 常见的时间复杂度有

  • 常数阶O(1)
  • 对数阶O(log2 n)
  • 线性阶O(n)
  • 线性对数阶O(n log2 n)
  • 平方阶O(n^2)
  • 立方阶O(n^3)
  • k次方阶O(n^K)
  • 指数阶O(2^n)

 计算方法

  1. 选取相对增长最高的项
  2. 最高项系数是都化为1
  3. 若是常数的话用O(1)表示

举个例子:如f(n)=3*n^4+3n+300 则 O(n)=n^4

通常我们计算时间复杂度都是计算最坏情况。计算时间复杂度的要注意的几个点

  • 如果算法的执行时间不随n的 增加而 增长,假如算法中有上千条语句,执行时间也不过是一个较大的常数。此类算法的时间复杂度是O(1)。 举例如下:代码执行100次,是一个常数,复杂度也是O(1)
    let x = 1;
    while (x <100) {
     x++;
    }
  • 多个循环语句时候,算法的时间复杂度是由嵌套层数最多的循环语句中最内层语句的方法决定的。举例如下:在下面for循环当中,外层循环每执行一次内层循环要执行n次,执行次数是根据n所决定的,时间复杂度是O(n^2)。 
  for (i = 0; i < n; i++){
         for (j = 0; j < n; j++) {
             // ...code
         }
     }
  • 循环不仅与n有关,还与执行循环判断条件有关。举例如下:在代码中,如果arr[i]不等于1的话,时间复杂度是O(n)。如果arr[i]等于1的话,循环不执行,时间复杂度是O(0)。 
    for(var i = 0; i<n && arr[i] !=1; i++) {
    // ...code
    }

 空间复杂度

空间复杂度是对一个算法在运行过程中临时占用存储空间的大小。

计算方法: 

  1. 忽略常数,用O(1)表示
  2. 递归算法的空间复杂度=(递归深度n)*(每次递归所要的辅助空间)

计算空间复杂度的简单几点

  • 仅仅只复制单个变量,空间复杂度为O(1)。举例如下:空间复杂度为O(n) = O(1)。
   let a = 1;
   let b = 2;
   let c = 3;
   console.log('输出a,b,c', a, b, c);
  • 递归实现,调用fun函数,每次都创建1个变量k。调用n次,空间复杂度O(n*1) = O(n)。
    function fun(n) {
       let k = 10;
       if (n == k) {
           return n;
       } else {
           return fun(++n)
       }
    }

测试数据:

    const array = [
      { id: 1, parentId: 0, name: "菜单1" },
      { id: 2, parentId: 0, name: "菜单2" },
      { id: 3, parentId: 0, name: "菜单3" },
      { id: 4, parentId: 1, name: "菜单4" },
      { id: 5, parentId: 1, name: "菜单5" },
      { id: 6, parentId: 2, name: "菜单6" },
      { id: 7, parentId: 4, name: "菜单7" },
      { id: 8, parentId: 7, name: "菜单8" },
      { id: 9, parentId: 8, name: "菜单9" },
      { id: 10, parentId: 9, name: "菜单10" },
      { id: 11, parentId: 10, name: "菜单11" },
      { id: 12, parentId: 11, name: "菜单12" },
      { id: 13, parentId: 12, name: "菜单13" },
      { id: 14, parentId: 13, name: "菜单14" },
    ];

二、扁平的数组转为树形结构

1. 性能不好(1W条数据需要 18s),实现较为简单:递归方式


/**
 * 方法一:简单递归
 * @param { Array } data 数据源
 * @param { Array } result 输出结果
 * @param { Number | String } parentId 根id
 */

const getChildren = (data, result = [], parentId) => {
  for (const item of data) {
    if (item.parentId === parentId) {
      const newItem = { ...item, children: [] };
      result.push(newItem);
      getChildren(data, newItem.children, item.id);
    }
  }
  return result;
};

const res2 = getChildren(array, [], 0);
console.log("res2", res2);

 

/**
 * 方法二:递归实现
 * @param { Array } list 数组
 * @param { String } parentId 父级 id
 * @param { Object } param2 可配置参数
 */

const generateTree = (
  list,
  parentId = 0,
  { idName = "id", parentIdName = "parentId", childName = "children" } = {}
) => {
  if (!Array.isArray(list)) {
    throw new Error("type only Array");
    // new Error("type only Array");
    return list;
  }
  return list.reduce((pre, cur) => {
    // 找到parentId 的子节点之后,递归找子节点的下一级节点
    if (cur[parentIdName] === parentId) {
      const children = generateTree(list, cur[idName]);
      if (children?.length) {
        cur[childName] = children;
      }
      return [...pre, cur];
    }
    return pre;
  }, []);
};
const result = generateTree(array, 0);


2. 性能可以,采用非递归方式

         应用了对象保存的是引用的特点,每次将当前节点的 id 作为 key,保存对应节点的引用信息,遍历数组时,每次更新 objMap 的 children 信息,这样 objMap中保留了所有节点极其子节点,最重要的是,我们只需要遍历一遍数组时间复杂度为O(n)。使用这种方式,1W数据 计算时长只需要60ms!

/**
 * 方法三:不用递归的简单循环
 * @param { Array } 源数据
 */

const arrayToTree = (items) => {
  const result = []; // 结果集
  const itemMap = {};

  // 先转成map存储
  for (const item of items) {
    itemMap[item.id] = { ...item, children: [] };
  }

  for (const item of items) {
    const id = item.id;
    const parentId = item.parentId;
    const treeItem = itemMap[id];

    if (parentId === 0) {
      result.push(treeItem);
    } else {
      if (!itemMap[parentId]) {
        itemMap[parentId] = { children: [] };
      }
      itemMap[parentId].children.push(treeItem);
    }
  }
  return result;
};

const res3 = arrayToTree(array);
console.log("res3", res3);

 

/**
 * 方法四:非递归实现 (映射 + 引用)
 * 前提:每一项都有parentId,根元素
 * @param { Array } list 数组
 * @param { String } rootId 根元素Id
 * @param { Object } param2 可配置参数
 */

const generateTree2 = (
  list,
  rootId = 0,
  { idName = "id", parentIdName = "parentId", childName = "childern" } = {}
) => {
  if (!Array.isArray(list)) {
    new Error("type only Array");
    return list;
  }
  const objMap = {}; //暂存数组以 id 为 key的映射关系
  const result = []; // 结果

  for (const item of list) {
    const id = item[idName];
    const parentId = item[parentIdName];

    // 该元素有可能已经放入map中,(找不到该项的parentId时 会先放入map
    objMap[id] = !objMap[id] ? item : { ...item, ...objMap[id] };

    const treeItem = objMap[id]; // 找到映射关系那一项(注意这里是引用)

    if (parentId === rootId) {
      // 已经到根元素则将映射结果放进结果集
      result.push(treeItem);
    } else {
      // 若父元素不存在,初始化父元素
      if (!objMap[parentId]) {
        objMap[parentId] = [];
      }

      // 若无该根元素则放入map中
      if (!objMap[parentId][childName]) {
        objMap[parentId][childName] = [];
      }
      objMap[parentId][childName].push(treeItem);
    }
  }
  return result;
};

const res = generateTree2(array);
console.log("res", res);

大佬原文地址1:1w条数据,平铺数组转树形结构https://juejin.cn/post/6988901231674523661#comment

大佬原文地址2:面试了十几个高级前端,竟然连(扁平数据结构转Tree)都写不出来https://juejin.cn/post/6983904373508145189?share_token=24aefc40-dd9e-465d-920b-7380e8728f2a#comment

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JavaScript 将扁平的数组输出转为树形结构(需要考虑性能) 的相关文章

  • Web 串行 API - 未捕获(承诺中)DOMException:无法打开串行端口/所需成员 baudRate 未定义

    下面的代码可以在我的 Xubuntu 机器上运行 但现在我在 Kubuntu 上 它不再工作了 它不会打开端口 Arduino IDE 工作正常 可以向开发板写入代码 并且我可以在 Chrome 中选择设备 Arduino Uno 但当我尝
  • 使用 useReducers 调度函数发送多个操作?

    使用时是否可以通过调度函数发送多个动作useReducer挂钩反应 我尝试向它传递一组操作 但这会引发未处理的运行时异常 明确地说 通常会有一个初始状态对象和一个减速器 如下所示 const initialState message1 nu
  • 使用 JavaScript 使链接保持活动状态并在单击时显示悬停效果

    I am struggling to make this work I d like to make it where if O F is clicked the hover state stays active if another li
  • 如何监听 jQuery AJAX 请求?

    以下两种实现 ajaxRequest 1 2 的方法应该是等效的 话说回来 为什么验证回调已执行的单元测试 3 在 1 中成功而在 2 中失败 我应该如何重写测试 3 来监视 2 中的成功回调 如果我尝试stub jQuery ajax使用
  • 音频 blob 的 URL.createObjectURL 在 Firefox 中给出 TypeError

    我正在尝试从创建的音频 blob 创建对象 URLgetUserMedia 该代码在 Chrome 中可以运行 但在 Firefox 中存在问题 错误 当我打电话时stopAudioRecorder 它停在audio player src
  • 如何将 Google Charts 与 Vue.js 库一起使用?

    我正在尝试使用 Vue js 库使用 Google Charts 制作图表 但我不知道如何添加到 div 这是我尝试做的 这是如何使用普通 javascript 添加图表 这是文档的代码示例 https developers google
  • 如何在谷歌地图android上显示多个标记

    我想在谷歌地图android上显示带有多个标记的位置 问题是当我运行我的应用程序时 它只显示一个位置 标记 这是我的代码 public class koordinatTask extends AsyncTask
  • 使用 Ajax.Request 将 JSON 从浏览器传递到 PHP 的最佳方法

    您好 我有一个 JSON 对象 它是一个二维数组 我需要使用 Ajax Request 将其传递给 PHP 我知道的唯一方法 现在我使用js函数手动序列化我的数组 并获取以下格式的数据 s 1 d 3 4等 我的问题是 有没有办法更直接 有
  • 跟踪用户何时点击浏览器上的后退按钮

    是否可以检测用户何时单击浏览器的后退按钮 我有一个 Ajax 应用程序 如果我可以检测到用户何时单击后退按钮 我可以显示适当的数据 任何使用 PHP JavaScript 的解决方案都是优选的 任何语言的解决方案都可以 只需要我可以翻译成
  • 使用 Newtonsoft 和 C# 反序列化嵌套 JSON

    我正在尝试解析来自 Rest API 的 Json 响应 我可以获得很好的响应并创建了一些类模型 我正在使用 Newtonsoft 的 Json Net 我的响应中不断收到空值 并且不确定我的模型设置是否正确或缺少某些内容 例如 我想要获取
  • 如何使输入字段和提交按钮变灰

    我想变灰这两件事 http doorsplit heroku com 歌曲输入字段和提交按钮 直到用户输入艺术家 有没有一种简单的方法可以通过 JQuery 来做到这一点 艺术家输入字段的id是 request artist 你可以这样做
  • Grails 在 javascript 内的 GSP 站点中使用 grails var

    我有一个在 GSP 文件中的 javascript 代码中使用 grails 变量值的问题 例如 我有一个会话值session getAttribute selectedValue 我想在 javascript 代码部分使用这个值 我现在的
  • Javascript 数组到 VBScript

    我有一个使用 Javascript 构建的对象数组 我需要使用 VBScript 读取它 如下例所示 我找不到在 VbScript 代码中循环遍历数组的方法myArray object 这个例子是我的问题的简化 我无法更改页面的默认语言 这
  • 为什么在 Internet Explorer 中访问 localStorage 对象会引发错误?

    我正在解决一个客户端问题 Modernizr 意外地没有检测到对localStorageInternet Explorer 9 中的对象 我的页面正确使用 HTML 5 文档类型 并且开发人员工具报告该页面具有 IE9 的浏览器模式和 IE
  • Javascript 纪元时间(以天为单位)

    我需要以天为单位的纪元时间 迄今为止 我已经看到过有关如何翻译它的帖子 但几天后就没有了 我对纪元时间很不好 我怎么能得到这个 我需要以天为单位的纪元时间 我将解释为您想要自纪元以来的天数 纪元本身是第 0 天 或第 1 天的开始 无论您如
  • Safari 支持 JavaScript window.onerror 吗?

    我有一个附加到 window onerror 的函数 window onerror function errorMsg url line window alert asdf 这在 firefox chrome 和 IE 中工作正常 但在 s
  • 如何更改此 jquery 插件的时区/时间戳?

    我正在使用这个名为 timeago 的插件 在这里找到 timeago yarp com 它工作得很好 只是它在似乎不同的时区运行 我住在美国东部 费城时区 当我将准确的 EST 时间放入 timeago 插件时 比如 2011 05 28
  • 如何通过SQL查询检查是否有JSON函数?

    有SQL 2016 中的 JSON 函数 https learn microsoft com en us sql t sql functions json functions transact sql例如 JSON VALUE JSON Q
  • JQuery 图像上传不适用于未来的活动

    我希望我的用户可以通过帖子上传图像 因此 每个回复表单都有一个上传表单 用户可以通过单击上传按钮上传图像 然后单击提交来提交帖子 现在我的上传表单可以上传第一个回复的图像 但第二个回复的上传不起作用 我的提交过程 Ajax 在 php 提交
  • 如何仅在最后一个
  • 处给出透明六边形角度?
  • 我必须制作这样的菜单 替代文本 http shup com Shup 330421 1104422739 My Desktop png http shup com Shup 330421 1104422739 My Desktop png

随机推荐

  • 爬虫 自动化工具-mongo-多线程爬虫

    一 selenium框架 1 selenium介绍 介绍 1 selenium是一个web自动化测试用的框架 程序员可以通过代码实现对浏览器的控制 比如打开网页 点 击网页中的元素 实现鼠标滚动等操作 2 它支持多款浏览器 如谷歌浏览器 火
  • spring---web项目结构分层

    一般的web结构 在前后台分离的情况下 我们对前端一般会以WEB API的形式同过JSON交互来与前端进行交互 一般来讲 我们的数据模型会在controller层进行交互 进行数据的校验与处理 然后交给service层进行相应的逻辑处理 如
  • OpenCV+ip摄像头实现远程实时监控

    一 项目准备 本项目所使用的内容有 1 ip摄像头app 主要依靠连接其ip来实现远程连接的效果 效果和遥控无人机所用的app功能类似 2 外接扩展显示屏 HDMI接口 这个不是必需品 但是多一个屏就方面观察 想自动识别某些物品并记录等等的
  • TS 对象可能为“未定义”,不能将类型“ XXXX

    前言 最近用 typeScript 也就是大家常说的 TS 写点东西 但是老是提醒这个未定义 那个可能为空 主要是 tsconfig json 中的严格模式我没关 所以今天总结一下 严格模式中 TS 中遇到 对象可能为 未定义 的具体场景
  • 资产扫描神器ARL增强改造

    拉取项目 首先从GitHub克隆到服务器上 git clone https github com ki9mu ARL plus docker 修改配置文件 因为ARL在配置文件里设置了黑名单 有时候项目为GOV或者EDU之类的时候无法进行扫
  • Java自定义实现字符串比较器-按照数字大小排序

    背景 在日常开发中 经常会遇到一些字符串排序的场景 场景一 字符串中包含的是纯数字 比较时想按照正常的数字大小进行排序 场景二 字符串中既包含数字又包含普通字符 比较时 普通字符想按照默认的字典进行排序 遇到字符串时则按照数字大小进行比较
  • Visual Stdio中使用番茄插件智能提示功能出问题。头文件也没有智能提示,甚至iostream都无法补全

    我在网上没有搜到同类型问题的解决办法 这个问题绝大部分人都不会遇到吧 不过我遇到了 问了大佬 也不知道什么原因 自己在大量乱点后 发现VA中的这个设置能解决问题 vs菜单栏的VASSIST gt Visual Assist Options
  • win7 64位 python3.4&opencv3.0配置安装教程

    一 安装Python 下载Python3 4 2 网址 https www python org downloads 注意 1 之前下载Python3 5后安装numpy出现了安装错误 尝试了各种解决办法 还是不能成功 看到网上有一条评论说
  • 粒子滤波(Particle filter)matlab实现

    粒子滤波是以贝叶斯推理和重要性采样为基本框架的 因此 想要掌握粒子滤波 对于上述两个基本内容必须有一个初步的了解 贝叶斯公式非常perfect 但是在实际问题中 由于变量维数很高 被积函数很难积分 常常会给粒子滤波带来很大的麻烦 为了克服这
  • Python 正则表达式

    最近研究Python爬虫 很多地方用到了正则表达式 但是没好好研究 每次都得现查文档 今天就专门看看Python正则表达式 本文参考了官方文档 re模块 模式 首先正则表达式的语法我就不说了 这玩意倒是不算难 用的时候现查就行了 正则表达式
  • 从0开始阿里云裸机安装java开发环境 Linux + Nginx+ MySQL+ Tomcat(lnmt)

    步骤1 更新阿里云的安装系统yum源 参考 https help aliyun com knowledge detail 5974184 html 参考 http blog csdn net endall article details 1
  • 数据库项目四总结:MySQL数据表的检索

    任务4 1 查询时选择列 1 基本查询语句 MySQL从数据表中查询数据的基本语句为SELECT语句 SELECT语句的基本格式是 SELECT lt 字段列表 gt FROM lt 表1 gt lt 表2 gt WHERE lt 表达式
  • MySQL的多表操作

    文章目录 1 多表关系 2 外键约束 2 1创建外键约束 2 2删除外键约束 3 对表联合查询 3 1交叉连接 3 2内连接 3 3外连接 4 子查询及子查询关键字 4 1 ALL关键字 4 2 ANY和SOME关键字 4 3 IN关键字
  • Python解析XML示例与解释

    使用工具包xml解析 python自带的工具 可以直接使用 使用示例如下 文章目录 简单案例 nodeType对应数字及其含义 简单案例
  • Sensor 结构——前照、背照、堆栈

    优异的工艺和技术可以使得即便不使用更新结构的CMOS 同样拥有更好的量子效率 固有热噪声 增益 满阱电荷 宽容度 灵敏度等关键型指标 在相同技术和工艺下 底大一级的确压死人 全画幅和aps c 人类的进步就是在不断发现问题 解决问题 背照式
  • redis master配置了密码进行主从同步

    1 如果master不设置密码 那么直接在slave服务器配置slaveof即可 配置如下 slaveof ip 端口 slaveof 221 224 85 186 6379 配置好我们看下redis的日志 看是否同步成功 5014 S 2
  • 生产线程池的定义与使用

    定义线程池 Slf4j Component public class PalmThreadPool public static int CORE POOL SIZE 4 private final AtomicInteger atomicI
  • C++ 中的常量,Const 关键字的用法(C++复习向p6)

    Const 常量 常量是固定的 程序执行期间不改变 又叫字面量 常量的类型 整数常量 0x23 浮点常量 1 23 布尔常量 true false 字符常量 n 字符串常量 nihao 定义常量 把字面量写成大写字母形式 是一个好习惯 方法
  • sql语句中分组取每组的最新数据

    今天敲sql的时候遇到了一个问题 业务流程是 检查记录 gt 整改通知 gt 整改回复 gt 检查组复查 如果复查不通过 则 检查组复查 gt 整改通知 gt 整改回复 gt 检查组复查 此时一条检查记录就可能对应多条整改通知去最新数据就用
  • JavaScript 将扁平的数组输出转为树形结构(需要考虑性能)

    扁平数组转为树形结构 做后台管理系统时也是经常用到的功能 面试时也是常常出现的 今天实现一下 引用两篇掘金大佬的文章 感谢大佬 一 什么是好算法 什么是坏算法 判断一个算法的好坏 一般从执行时间和占用空间来看 执行时间越短 占用的内存空间越