用户时到运转 發表於 2021-2-4 15:54:00

JavaScript 构造树形结构的一种高效算法

<div id="cnblogs_post_body" class="blogpost-body cnblogs-markdown">
<h3 id="引言">引言</h3>
<p>我们经常会碰到树形数据结构,比如组织层级、省市县或者动植物分类等等数据。下面是一个树形结构的例子:</p>
<p><img src="https://upload-images.jianshu.io/upload_images/1618526-afcc0d3ca19a31ce.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="树形结构" loading="lazy"></p>
<p>在实际应用中,比较常见的做法是将这些信息存储为下面的结构,特别是当存在1对多的父/子节点关系时:</p>
<pre><code class="hljs yaml"><span class="hljs-string">const</span> <span class="hljs-string">data</span> <span class="hljs-string">=</span> [
{ <span class="hljs-attr">id:</span> <span class="hljs-number">56</span>, <span class="hljs-attr">parentId:</span> <span class="hljs-number">62</span> },
{ <span class="hljs-attr">id:</span> <span class="hljs-number">81</span>, <span class="hljs-attr">parentId:</span> <span class="hljs-number">80</span> },
{ <span class="hljs-attr">id:</span> <span class="hljs-number">74</span>, <span class="hljs-attr">parentId:</span> <span class="hljs-literal">null</span> },
{ <span class="hljs-attr">id:</span> <span class="hljs-number">76</span>, <span class="hljs-attr">parentId:</span> <span class="hljs-number">80</span> },
{ <span class="hljs-attr">id:</span> <span class="hljs-number">63</span>, <span class="hljs-attr">parentId:</span> <span class="hljs-number">62</span> },
{ <span class="hljs-attr">id:</span> <span class="hljs-number">80</span>, <span class="hljs-attr">parentId:</span> <span class="hljs-number">86</span> },
{ <span class="hljs-attr">id:</span> <span class="hljs-number">87</span>, <span class="hljs-attr">parentId:</span> <span class="hljs-number">86</span> },
{ <span class="hljs-attr">id:</span> <span class="hljs-number">62</span>, <span class="hljs-attr">parentId:</span> <span class="hljs-number">74</span> },
{ <span class="hljs-attr">id:</span> <span class="hljs-number">86</span>, <span class="hljs-attr">parentId:</span> <span class="hljs-number">74</span> },
]<span class="hljs-string">;</span>
</code></pre>
<p>那么,如何将这种对象数组格式转换为层级树的格式呢?其实,利用 JavaScript 对象引用的特性,实现起来会非常简单。它可以不用递归,在O(n)时间内完成。</p>
<h4 id="术语">术语</h4>
<p>为了表述方便,我们先来定义几个术语。我们把数组中的每个元素(也就树形图里的每个圆圈)称为<strong>“节点”</strong>。节点可以是多个节点的<strong>“父节点”</strong>,也可以是某个节点的<strong>“子节点”</strong>。上图中,<code>节点 86</code>是<code>节点 80</code>&nbsp;和<code>节点 87</code>的“父节点”,<code>节点 86</code>&nbsp;是<code>节点 74</code>的子节点。树的最顶部节点称为<strong>“根节点”</strong>。</p>
<h3 id="思路">思路</h3>
<p>为了构造树形结构,我们需要:</p>
<ol>
<li>遍历<code>data</code>数组</li>
<li>找到当前元素的父元素</li>
<li>在父元素对象上添加一个对该子元素的引用</li>
<li>元素如果没有父元素,那我们就认为它是树的根节点</li>
</ol>
<p>我们可以看到到,引用被保存在对象树下,这就是为什么我们可以在O(n)时间内完成这个任务!</p>
<h3 id="建立-id-数组索引映射关系">建立 ID-数组索引映射关系</h3>
<p>虽然不是必需的,但是这个映射关系可以帮我们快速找到元素的位置,方便找到到父元素的引用。</p>
<pre><code class="hljs haskell"><span class="hljs-title">const</span> idMapping = <span class="hljs-class"><span class="hljs-keyword">data</span>.reduce((<span class="hljs-title">acc</span>, <span class="hljs-title">el</span>, <span class="hljs-title">i</span>) =&gt; {
<span class="hljs-title">acc</span>[<span class="hljs-title">el</span>.<span class="hljs-title">id</span>] = <span class="hljs-title">i</span>;
<span class="hljs-title">return</span> <span class="hljs-title">acc</span>;
}, {});</span>
</code></pre>
<p>映射结果如下,后面你会看到它的用处有多大:</p>
<pre><code class="hljs yaml">{
<span class="hljs-attr">56:</span> <span class="hljs-number">0</span>,
<span class="hljs-attr">62:</span> <span class="hljs-number">7</span>,
<span class="hljs-attr">63:</span> <span class="hljs-number">4</span>,
<span class="hljs-attr">74:</span> <span class="hljs-number">2</span>,
<span class="hljs-attr">76:</span> <span class="hljs-number">3</span>,
<span class="hljs-attr">80:</span> <span class="hljs-number">5</span>,
<span class="hljs-attr">81:</span> <span class="hljs-number">1</span>,
<span class="hljs-attr">86:</span> <span class="hljs-number">8</span>,
<span class="hljs-attr">87:</span> <span class="hljs-number">6</span>,
}<span class="hljs-string">;</span>
</code></pre>
<h3 id="构造树形结构">构造树形结构</h3>
<p>现在我们开始构造这个树形结构。遍历这个对象数组,找到每个元素的父元素对象,然后添加对这个元素的引用。现在你应该看到了,这个&nbsp;<code>idMapping</code>用来定位元素的位置多么方便(常数时间)。</p>
<pre><code class="hljs javascript"><span class="hljs-keyword">let</span> root;
data.forEach(<span class="hljs-function"><span class="hljs-params">el</span> =&gt;</span> {
<span class="hljs-comment">// 判断根节点</span>
<span class="hljs-keyword">if</span> (el.parentId === <span class="hljs-literal">null</span>) {
    root = el;
    <span class="hljs-keyword">return</span>;
}
<span class="hljs-comment">// 用映射表找到父元素</span>
<span class="hljs-keyword">const</span> parentEl = data];
<span class="hljs-comment">// 把当前元素添加到父元素的`children`数组中</span>
parentEl.children = [...(parentEl.children || []), el];
});
</code></pre>
<p>完事!用<code>console.log</code>&nbsp;打印&nbsp;<code>root</code>&nbsp;看下:</p>
<pre><code class="hljs coffeescript"><span class="hljs-built_in">console</span>.log(root);
</code></pre>
<pre><code class="hljs yaml">{
<span class="hljs-attr">id:</span> <span class="hljs-number">74</span>,
<span class="hljs-attr">parentId:</span> <span class="hljs-literal">null</span>,
<span class="hljs-attr">children:</span> [
    {
      <span class="hljs-attr">id:</span> <span class="hljs-number">62</span>,
      <span class="hljs-attr">parentId:</span> <span class="hljs-number">74</span>,
      <span class="hljs-attr">children:</span> [{ <span class="hljs-attr">id:</span> <span class="hljs-number">56</span>, <span class="hljs-attr">parentId:</span> <span class="hljs-number">62</span> }, { <span class="hljs-attr">id:</span> <span class="hljs-number">63</span>, <span class="hljs-attr">parentId:</span> <span class="hljs-number">62</span> }],
    },
    {
      <span class="hljs-attr">id:</span> <span class="hljs-number">86</span>,
      <span class="hljs-attr">parentId:</span> <span class="hljs-number">74</span>,
      <span class="hljs-attr">children:</span> [
      {
          <span class="hljs-attr">id:</span> <span class="hljs-number">80</span>,
          <span class="hljs-attr">parentId:</span> <span class="hljs-number">86</span>,
          <span class="hljs-attr">children:</span> [{ <span class="hljs-attr">id:</span> <span class="hljs-number">81</span>, <span class="hljs-attr">parentId:</span> <span class="hljs-number">80</span> }, { <span class="hljs-attr">id:</span> <span class="hljs-number">76</span>, <span class="hljs-attr">parentId:</span> <span class="hljs-number">80</span> }],
      },
      { <span class="hljs-attr">id:</span> <span class="hljs-number">87</span>, <span class="hljs-attr">parentId:</span> <span class="hljs-number">86</span> },
      ],
    },
],
}<span class="hljs-string">;</span>
</code></pre>
<h3 id="原理">原理</h3>
<p>为什么可以这么做呢?这是因为,<code>data</code>&nbsp;数组里的每个元素都是内存里的一个对象引用,&nbsp;<code>forEach</code>循环里的<code>el</code>变量其实是指向内存里的一个对象,<code>parentEl</code>也引用了一个对象。</p>
<p>如果内存中的一个对象引用了一个 children 数组,这些子元素同样可以引用自己的子元素数组,这些关联关系都是通过引用完成的。</p>
<h3 id="总结">总结</h3>
<p>对象引用是 JavaScript 中最基本的概念之一,需要更多的学习和理解。真正理解这个概念后,既可以避免棘手的 bug,又可以为看似复杂的问题提供相对简单的解决方案。</p>
<p>&nbsp;</p>


</div>
<p>&nbsp;</p>
<p>出处:https://www.cnblogs.com/lzkwin/p/12143458.html</p>
<p>&nbsp;</p>

</div>
<div id="MySignature" role="contentinfo">
    <div class="div_masklayer" id="div_masklayer"></div>
<div class="div_popup" id="Div_popup">
<div style="float:left;width:50%">
<img class="img_zfb" id="img_zfb" src="https://images.cnblogs.com/cnblogs_com/mq0036/508398/o_12.png" />
</div>
<div style="float:left;width:50%">
<img class="img_zfb" id="img_zfb" src="https://images.cnblogs.com/cnblogs_com/mq0036/508398/o_200921131119o_12.png">
</div>
<p class="mid">您的资助是我最大的动力!<br>金额随意,欢迎来赏!<br>
<span style="color: #f9f">付</span>款后有任何问题请给我留言。</p>
</div>


<div class="autograph">
    <p> <span style="display: none"> 如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【<strong>推荐</strong>】按钮。<br>
      </span> 如果,您希望更容易地发现我的新博客,不妨点击一下绿色通道的【<strong>关注我</strong>】。(●'◡'●)</p>
    <p>因为,我的写作热情也离不开您的肯定与支持,感谢您的阅读,我是【<strong>Jack_孟</strong>】!</p>
    <div class="blogds">
      <b style="color:#f00;font-size: 22px">如果对你有所帮助,赞助一杯咖啡!打</b>
      
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
      <b style="color: #f00; font-size: 22px">付款后有任何问题请给我留言!!!</b>
    </div>
    <p>本文来自博客园,作者:jack_Meng,转载请注明原文链接:https://www.cnblogs.com/mq0036/p/14373063.html</p>
    <p>【免责声明】本文来自源于网络,如涉及版权或侵权问题,请及时联系我们,我们将第一时间删除或更改!</p>
</div><br><br>
来源:https://www.cnblogs.com/mq0036/p/14373063.html
頁: [1]
查看完整版本: JavaScript 构造树形结构的一种高效算法