凹入表形式打印树形结构_JavaScript 构造树形结构的一种高效算法

e560d4e44f7033240d624b389da9ab6a.png

作者简介:

李中凯老师,8年前端开发,前端负责人,擅长JavaScript/Vue。

掘金文章专栏:KaysonLi 的个人主页 - 专栏 - 掘金

主要分享:Vue.js, JavaScript,CSS

引言

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

ccd4ef77c865c3a7976d02658c7f3e01.png

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

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的子节点。树的最顶部节点称为“根节点”。

思路

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

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

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

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

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

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

关于数组 reduce 方法,推荐阅读:自从学会了 Array.reduce() ,再也离不开它

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

{
  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,又可以为看似复杂的问题提供相对简单的解决方案。

本文已经获得李中凯老师授权转发,其他人若有兴趣转载,请直接联系作者授权。

作者简介:

李中凯老师,8年前端开发,前端负责人,擅长JavaScript/Vue。

掘金文章专栏:KaysonLi 的个人主页 - 专栏 - 掘金

主要分享:Vue.js, JavaScript,CSS

更多的学习资料,请看这里:

http://www.jnshu.com/login/1/36856070?source=zhihu-article-lizhongkai

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值