zoukankan      html  css  js  c++  java
  • JavaScript 构造树形结构的一种高效算法

    引言

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

    树形结构

    在实际应用中,比较常见的做法是将这些信息存储为下面的结构,特别是当存在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,又可以为看似复杂的问题提供相对简单的解决方案。

    出处:https://www.cnblogs.com/lzkwin/p/12143458.html

    您的资助是我最大的动力!
    金额随意,欢迎来赏!
    款后有任何问题请给我留言。

    如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的推荐按钮。
    如果,您希望更容易地发现我的新博客,不妨点击一下绿色通道的关注我。(●'◡'●)

    如果你觉得本篇文章对你有所帮助,请给予我更多的鼓励,求打             付款后有任何问题请给我留言!!!

    因为,我的写作热情也离不开您的肯定支持,感谢您的阅读,我是【Jack_孟】!

  • 相关阅读:
    安卓日志输出-logger
    RecyclerView的使用(3)之加入Header和Footer
    这些年我踩过的坑——Android
    精简点名IAP错误
    Android中BitmapFactory.Options详解
    在Android下通过ExifInterface类操作图片的Exif信息
    android 生成随机数
    JSON入门之二:org.json的基本使用方法
    Android中的各种访问权限Permission含义
    jar命令的用法详解
  • 原文地址:https://www.cnblogs.com/mq0036/p/14373063.html
Copyright © 2011-2022 走看看