引言

我们经常会碰到树形数据结构,比如组织层级、省市县或者动植物分类等等数据。下面是一个树形结构的例子:

在实际应用中,比较常见的做法是将这些信息存储为下面的结构,特别是当存在1对多的父/子节点关系时:

  1. const data = [
  2. { id: 56, parentId: 62 },
  3. { id: 81, parentId: 80 },
  4. { id: 74, parentId: null },
  5. { id: 76, parentId: 80 },
  6. { id: 63, parentId: 62 },
  7. { id: 80, parentId: 86 },
  8. { id: 87, parentId: 86 },
  9. { id: 62, parentId: 74 },
  10. { id: 86, parentId: 74 },
  11. ];

那么,如何将这种对象数组格式转换为层级树的格式呢?其实,利用 JavaScript 对象引用的特性,实现起来会非常简单。它可以不用递归,在O(n)时间内完成。

术语

为了表述方便,我们先来定义几个术语。我们把数组中的每个元素(也就树形图里的每个圆圈)称为“节点”。节点可以是多个节点的“父节点”,也可以是某个节点的“子节点”。上图中,节点 86节点 80 和节点 87的“父节点”,节点 86 是节点 74的子节点。树的最顶部节点称为“根节点”

思路

为了构造树形结构,我们需要:

  1. 遍历data数组
  2. 找到当前元素的父元素
  3. 在父元素对象上添加一个对该子元素的引用
  4. 元素如果没有父元素,那我们就认为它是树的根节点

我们可以看到到,引用被保存在对象树下,这就是为什么我们可以在O(n)时间内完成这个任务!

建立 ID-数组索引映射关系

虽然不是必需的,但是这个映射关系可以帮我们快速找到元素的位置,方便找到到父元素的引用。

  1. const idMapping = data.reduce((acc, el, i) => {
  2. acc[el.id] = i;
  3. return acc;
  4. }, {});

映射结果如下,后面你会看到它的用处有多大:

  1. {
  2. 56: 0,
  3. 62: 7,
  4. 63: 4,
  5. 74: 2,
  6. 76: 3,
  7. 80: 5,
  8. 81: 1,
  9. 86: 8,
  10. 87: 6,
  11. };

构造树形结构

现在我们开始构造这个树形结构。遍历这个对象数组,找到每个元素的父元素对象,然后添加对这个元素的引用。现在你应该看到了,这个 idMapping用来定位元素的位置多么方便(常数时间)。

  1. let root;
  2. data.forEach(el => {
  3. // 判断根节点
  4. if (el.parentId === null) {
  5. root = el;
  6. return;
  7. }
  8. // 用映射表找到父元素
  9. const parentEl = data[idMapping[el.parentId]];
  10. // 把当前元素添加到父元素的`children`数组中
  11. parentEl.children = [...(parentEl.children || []), el];
  12. });

完事!用console.log 打印 root 看下:

  1. console.log(root);
  1. {
  2. id: 74,
  3. parentId: null,
  4. children: [
  5. {
  6. id: 62,
  7. parentId: 74,
  8. children: [{ id: 56, parentId: 62 }, { id: 63, parentId: 62 }],
  9. },
  10. {
  11. id: 86,
  12. parentId: 74,
  13. children: [
  14. {
  15. id: 80,
  16. parentId: 86,
  17. children: [{ id: 81, parentId: 80 }, { id: 76, parentId: 80 }],
  18. },
  19. { id: 87, parentId: 86 },
  20. ],
  21. },
  22. ],
  23. };

原理

为什么可以这么做呢?这是因为,data 数组里的每个元素都是内存里的一个对象引用, forEach循环里的el变量其实是指向内存里的一个对象,parentEl也引用了一个对象。

如果内存中的一个对象引用了一个 children 数组,这些子元素同样可以引用自己的子元素数组,这些关联关系都是通过引用完成的。

总结

对象引用是 JavaScript 中最基本的概念之一,需要更多的学习和理解。真正理解这个概念后,既可以避免棘手的 bug,又可以为看似复杂的问题提供相对简单的解决方案。

欢迎关注微信公众号:1024译站

JavaScript 构造树形结构的一种高效算法的更多相关文章

  1. 【WPF】树形结构TreeView的用法(MVVM)

    TreeView控件的用法还是有蛮多坑点的,最好记录一下. 参考项目: https://www.codeproject.com/Articles/26288/Simplifying-the-WPF-T ...

  2. Java编程:将具有父子关系的数据库表数据转换为树形结构,支持无限层级

    在平时的开发工作中,经常遇到这样一个场景,在数据库中存储了具有父子关系的数据,需要将这些数据以树形结构的形式在界面上进行展示.本文的目的是提供了一个通用的编程模型,解决将具有父子关系的数据转换成树形结 ...

  3. javascript将平行的拥有上下级关系的数据转换成树形结构

    转换函数 var Littlehow = {}; /** * littlehow 2019-05-15 * 平行数据树形转换器 * @type {{format: tree.format, sort: ...

  4. MongoDB五种树形结构表示法

    MongoDB五种树形结构表示法 第一种:父链接结构 db.categories.insert( { _id: "MongoDB", parent: "Databases ...

  5. javascript通过递归改子节点数据-用于层级深度未知的树形结构

    最近在做这么个需求:树形结构,层级深度未知,一旦某个节点的状态是置灰的话,其所有子节点都要置灰. 方案一(数据库有值):如果数据库里置灰节点的所有子节点,值也都是"置灰",那后台取 ...

  6. JavaScript中常见的十五种设计模式

    在程序设计中有很多实用的设计模式,而其中大部分语言的实现都是基于“类”. 在JavaScript中并没有类这种概念,JS中的函数属于一等对象,在JS中定义一个对象非常简单(var obj = {}), ...

  7. 树形结构的数据库表Schema设计-基于左右值编码

    树形结构的数据库表Schema设计 程序设计过程中,我们常常用树形结构来表征某些数据的关联关系,如企业上下级部门.栏目结构.商品分类等等,通常而言,这些树状结构需要借助于数据库完 成持久化.然而目前的 ...

  8. 浅谈树形结构的特性和应用(上):多叉树,红黑树,堆,Trie树,B树,B+树...

    上篇文章我们主要介绍了线性数据结构,本篇233酱带大家康康 无所不在的非线性数据结构之一:树形结构的特点和应用. 树形结构,是指:数据元素之间的关系像一颗树的数据结构.我们看图说话: 它具有以下特点: ...

  9. C# EasyUI树形结构权限管理模块

    最近悟出来一个道理,在这儿分享给大家:学历代表你的过去,能力代表你的现在,学习代表你的将来. 十年河东十年河西,莫欺少年穷 学无止境,精益求精 本节和大家探讨下C#使用EasyUI树形结构/Tree构 ...

随机推荐

  1. javascript中json对象与字符串互转及取值

    一.   json字符串转换为javascript对象,并取值 var answer = '{"id":0}' var value= JSON.parse(answer); //转 ...

  2. @codechef - BIKE@ Chef and Bike

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 输入 n(n ≤ 22) 个点,m(m ≤ 8000) 个边.每 ...

  3. div 禁止点击

    今天需要这个需求,原来真的有 style="pointer-events: none;"

  4. selenium webdriver学习(八)------------如何操作select下拉框(转)

    selenium webdriver学习(八)------------如何操作select下拉框 博客分类: Selenium-webdriver   下面我们来看一下selenium webdriv ...

  5. sublime简介

    Sublime Text是一个代码编辑器.也是HTML和散文先进的文本编辑器.漂亮的用户界面和非凡的功能,例如:多选择,Python插件,代码段等等.完全可自定义键绑定,菜单和工具栏等等.漂亮的用户界 ...

  6. 利用 jquery 获取某个元素下的所有图片并改变其属性

    HTML代码 <div id="mochu"> <p>内容....<./p> <p><img src="xxxx.p ...

  7. CSP-J真题分析之中央处理器(CPU)

    中央处理器(CPU,central processing unit)作为计算机系统的运算和控制核心,是信息处理.程序运行的最终执行单元.CPU 自产生以来,在逻辑结构.运行效率以及功能外延上取得了巨大 ...

  8. Mule ESB 自带例子hello初体验

    1 配置的流的效果图 2 应用配置文件hello.xml内容 <?xml version="1.0" encoding="UTF-8"?> < ...

  9. 51nod1160 压缩算法的矩阵——一道有趣的题

    https://blog.csdn.net/lunch__/article/details/82655579 看似高大上,实际也不太好想到 先尝试确定一些位: 给出了最后一列,sort得到第一列 0X ...

  10. mysql 第三课 jdbc基础操作

    jdbc连接可以大致分为5步: 1.注册驱动 2.获取连接 3.编写语句 4.执行语句 5.关闭连接 其中可以设置参数等等. 1.我们先建一个项目目录: 其中com.etc.dao为数据访问对象 co ...