引言

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

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

const data = [
{ id: 56, parentId: 62 },
{ id: 81, parentId: 80 },
{ id: 74, parentId: null },
{ id: 76, parentId: 80 },
{ id: 63, parentId: 62 },
{ id: 80, parentId: 86 },
{ id: 87, parentId: 86 },
{ id: 62, parentId: 74 },
{ id: 86, parentId: 74 },
];

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

术语

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

思路

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

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

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

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

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

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

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

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

构造树形结构

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

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

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

console.log(root);
{
id: 74,
parentId: null,
children: [
{
id: 62,
parentId: 74,
children: [{ id: 56, parentId: 62 }, { id: 63, parentId: 62 }],
},
{
id: 86,
parentId: 74,
children: [
{
id: 80,
parentId: 86,
children: [{ id: 81, parentId: 80 }, { id: 76, parentId: 80 }],
},
{ id: 87, parentId: 86 },
],
},
],
};

原理

为什么可以这么做呢?这是因为,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. oralce CUBE

    select id,area,stu_type,sum(score) score from students group by cube(id,area,stu_type) order by id,a ...

  2. 关于IOS 微信浏览器 点击输入框自动放大问题

    <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0& ...

  3. PMC亮相IDF展示12G SAS分层存储解决方式

    引领大数据连接.传送以及存储,提供创新半导体及软件解决方式的PMC公司(纳斯达克代码:PMCS)出席了2014年4月2-3日在深圳举办的2014 IDF英特尔开发人员论坛. 此次,PMC将在 1层展示 ...

  4. [转]Java多线程学习(总结很详细!!!)

    Java多线程学习(总结很详细!!!) 此文只能说是java多线程的一个入门,其实Java里头线程完全可以写一本书了,但是如果最基本的你都学掌握好,又怎么能更上一个台阶呢? 本文主要讲java中多线程 ...

  5. Java反射机制(二):通过反射取得类的结构

    在反射运用过程中,如果你想得到一个类的完整结构,那么就要使用到java.lang.reflect包中的几个类: · Constructor  表示类中的构造方法 · Field  表示类中的属性 · ...

  6. laravel5.*安装使用Redis以及解决Class 'Predis\Client' not found和Fatal error: Non-static method Redis::set() cannot be called statically错误

    https://phpartisan.cn/news/35.html laravel中我们可以很简单的使用Redis,如何在服务器安装Redis以及原创访问你们可以访问Ubuntu 设置Redis密码 ...

  7. Editplus配置java编译运行环境

    1.进入配置环境界面 首先,从菜单"工具(Tools)"->"配置用户工具..."进入用户工具设置. 在类别里展开"工具"树形菜单-& ...

  8. poj 2993

    跟poj 2996反过来了,这里比较麻烦的就是处理白棋和黑棋各棋子对应的位置 还有在最后打印棋盘式|,:,.的时候会有点繁琐(- - ACMer新手 ): 直接看代码吧: #include<cs ...

  9. H3C OSPF基本配置命令

  10. python模块之random模块

    random模块 随机模块,用于处理随机问题. import random # 随机整数 print(random.randint(0, 9)) # 0到9之间随机一个整数 print(random. ...