JavaScript实现树结构(一)
<h2 id="javascript实现树结构一">JavaScript实现树结构(一)</h2><h3 id="一树结构简介">一、树结构简介</h3>
<h4 id="11简单了解树结构">1.1.简单了解树结构</h4>
<p><strong>什么是树?</strong></p>
<p>真实的树:</p>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%A0%91%E4%B8%80/1.png" alt="image-20200229205530929" loading="lazy"></p>
<p><strong>树的特点:</strong></p>
<ul>
<li>树一般都有一个<strong>根</strong>,连接着根的是<strong>树干</strong>;</li>
<li>树干会发生分叉,形成许多<strong>树枝</strong>,树枝会继续分化成更小的<strong>树枝</strong>;</li>
<li>树枝的最后是<strong>叶子</strong>;</li>
</ul>
<p>现实生活中很多结构都是树的抽象,模拟的树结构相当于旋转<code>180°</code>的树。</p>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%A0%91%E4%B8%80/2.png" alt="image-20200229205630945" loading="lazy"></p>
<p><strong>树结构对比于数组/链表/哈希表有哪些优势呢:</strong></p>
<p><strong>数组:</strong></p>
<ul>
<li>优点:可以通过<strong>下标值访问</strong>,效率高;</li>
<li>缺点:查找数据时需要先对数据进行<strong>排序</strong>,生成<strong>有序数组</strong>,才能提高查找效率;并且在插入和删除元素时,需要大量的<strong>位移操作</strong>;</li>
</ul>
<p><strong>链表:</strong></p>
<ul>
<li>优点:数据的插入和删除操作效率都很高;</li>
<li>缺点:<strong>查找</strong>效率低,需要从头开始依次查找,直到找到目标数据为止;当需要在链表中间位置插入或删除数据时,插入或删除的效率都不高。</li>
</ul>
<p><strong>哈希表:</strong></p>
<ul>
<li>优点:哈希表的插入/查询/删除效率都非常高;</li>
<li>缺点:<strong>空间利用率不高</strong>,底层使用的数组中很多单元没有被利用;并且哈希表中的元素是<strong>无序</strong>的,不能按照固定顺序遍历哈希表中的元素;而且不能快速找出哈希表中<strong>最大值或最小值</strong>这些特殊值。</li>
</ul>
<p><strong>树结构:</strong></p>
<p>优点:树结构综合了上述三种结构的优点,同时也弥补了它们存在的缺点(虽然效率不一定都比它们高),比如树结构中数据都是有序的,查找效率高;空间利用率高;并且可以快速获取最大值和最小值等。</p>
<p>总的来说:<strong>每种数据结构都有自己特定的应用场景</strong></p>
<p><strong>树结构:</strong></p>
<ul>
<li><strong>树(Tree)</strong>:由 n(n ≥ 0)个节点构成的<strong>有限集合</strong>。当 n = 0 时,称为<strong>空树</strong>。</li>
</ul>
<p>对于任一棵非空树(n > 0),它具备以下性质:</p>
<ul>
<li>数中有一个称为<strong>根(Root)</strong>的特殊节点,用 **r **表示;</li>
<li>其余节点可分为 m(m > 0)个互不相交的有限集合 T<sub>1</sub>,T<sub>2</sub>,...,T<sub>m</sub>,其中每个集合本身又是一棵树,称为原来树的<strong>子树(SubTree)</strong>。</li>
</ul>
<p><strong>树的常用术语:</strong></p>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%A0%91%E4%B8%80/3.png" alt="image-20200229221126468" loading="lazy"></p>
<ul>
<li><strong>节点的度(Degree)</strong>:节点的<strong>子树个数</strong>,比如节点B的度为2;</li>
<li><strong>树的度</strong>:树的所有节点中<strong>最大的度数</strong>,如上图树的度为2;</li>
<li><strong>叶节点(Leaf)</strong>:<strong>度为0的节点</strong>(也称为叶子节点),如上图的H,I等;</li>
<li><strong>父节点(Parent)</strong>:度不为0的节点称为父节点,如上图节点B是节点D和E的父节点;</li>
<li><strong>子节点(Child)</strong>:若B是D的父节点,那么D就是B的子节点;</li>
<li><strong>兄弟节点(Sibling)</strong>:具有同一父节点的各节点彼此是兄弟节点,比如上图的B和C,D和E互为兄弟节点;</li>
<li><strong>路径和路径长度</strong>:路径指的是一个节点到另一节点的通道,路径所包含边的个数称为路径长度,比如A->H的路径长度为3;</li>
<li><strong>节点的层次(Level)</strong>:规定<strong>根节点在1层</strong>,其他任一节点的层数是其父节点的<strong>层数加1</strong>。如B和C节点的层次为2;</li>
<li><strong>树的深度(Depth)</strong>:树种所有节点中的<strong>最大层次</strong>是这棵树的深度,如上图树的深度为4;</li>
</ul>
<h4 id="12树结构的表示方式">1.2.树结构的表示方式</h4>
<ul>
<li><strong>最普通的表示方法</strong>:</li>
</ul>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%A0%91%E4%B8%80/4.png" alt="image-20200229230417613" loading="lazy"></p>
<p>如图,树结构的组成方式类似于链表,都是由一个个节点连接构成。不过,根据每个父节点子节点数量的不同,每一个父节点需要的引用数量也不同。比如节点A需要3个引用,分别指向子节点B,C,D;B节点需要2个引用,分别指向子节点E和F;K节点由于没有子节点,所以不需要引用。</p>
<p>这种方法缺点在于我们无法确定某一结点的引用数。</p>
<ul>
<li><strong>儿子-兄弟表示法</strong>:</li>
</ul>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%A0%91%E4%B8%80/5.png" alt="image-20200229232805477" loading="lazy"></p>
<p>这种表示方法可以完整地记录每个节点的数据,比如:</p>
<pre><code>//节点A
Node{
//存储数据
this.data = data
//统一只记录左边的子节点
this.leftChild = B
//统一只记录右边的第一个兄弟节点
this.rightSibling = null
}
//节点B
Node{
this.data = data
this.leftChild = E
this.rightSibling = C
}
//节点F
Node{
this.data = data
this.leftChild = null
this.rightSibling = null
}
</code></pre>
<p>这种表示法的优点在于每一个节点中引用的数量都是确定的。</p>
<ul>
<li><strong>儿子-兄弟表示法旋转</strong></li>
</ul>
<p>以下为儿子-兄弟表示法组成的树结构:</p>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%A0%91%E4%B8%80/6.png" alt="image-20200229234549049" loading="lazy"></p>
<p>将其顺时针旋转45°之后:</p>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%A0%91%E4%B8%80/7.png" alt="image-20200229235549522" loading="lazy"></p>
<p>这样就成为了一棵<strong>二叉树</strong>,由此我们可以得出结论:<strong>任何树都可以通过二叉树进行模拟</strong>。但是这样父节点不是变了吗?其实,父节点的设置只是为了方便指向子节点,在代码实现中谁是父节点并没有关系,只要能正确找到对应节点即可。</p>
<h3 id="二二叉树">二、二叉树</h3>
<h4 id="21二叉树简介">2.1.二叉树简介</h4>
<p><strong>二叉树的概念</strong>:如果树中的每一个节点最多只能由<strong>两个子节点</strong>,这样的树就称为<strong>二叉树</strong>;</p>
<p>二叉树十分重要,不仅仅是因为简单,更是因为几乎所有的树都可以表示成二叉树形式。</p>
<p><strong>二叉树的组成</strong>:</p>
<ul>
<li>二叉树可以为空,也就是没有节点;</li>
<li>若二叉树不为空,则它由根节点和称为其左子树TL和右子树TR的两个不相交的二叉树组成;</li>
</ul>
<p><strong>二叉树的五种形态</strong>:</p>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%A0%91%E4%B8%80/8.png" alt="image-20200301001718079" loading="lazy"></p>
<p>上图分别表示:空的二叉树、只有一个节点的二叉树、只有左子树TL的二叉树、只有右子树TR的二叉树和有左右两个子树的二叉树。</p>
<p><strong>二叉树的特性</strong>:</p>
<ul>
<li>一个二叉树的第 i 层的最大节点树为:2<sup>(i-1)</sup>,i >= 1;</li>
<li>深度为k的二叉树的最大节点总数为:2<sup>k</sup> - 1 ,k >= 1;</li>
<li>对任何非空二叉树,若 n<sub>0</sub> 表示叶子节点的个数,n<sub>2</sub>表示度为2的非叶子节点个数,那么两者满足关系:n<sub>0</sub> = n<sub>2</sub> + 1;如下图所示:H,E,I,J,G为叶子节点,总数为5;A,B,C,F为度为2的非叶子节点,总数为4;满足n<sub>0</sub> = n<sub>2</sub> + 1的规律。</li>
</ul>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%A0%91%E4%B8%80/9.png" alt="image-20200301092140211" loading="lazy"></p>
<h4 id="22特殊的二叉树">2.2.特殊的二叉树</h4>
<p><strong>完美二叉树</strong></p>
<p>完美二叉树(Perfect Binary Tree)也成为满二叉树(Full Binary Tree),在二叉树中,除了最下一层的叶子节点外,每层节点都有2个子节点,这就构成了完美二叉树。</p>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%A0%91%E4%B8%80/10.png" alt="image-20200301093237681" loading="lazy"></p>
<p><strong>完全二叉树</strong></p>
<p>完全二叉树(Complete Binary Tree):</p>
<ul>
<li>除了二叉树最后一层外,其他各层的节点数都达到了最大值;</li>
<li>并且,最后一层的叶子节点从左向右是连续存在,只缺失右侧若干叶子节点;</li>
<li>完美二叉树是特殊的完全二叉树;</li>
</ul>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%A0%91%E4%B8%80/11.png" alt="image-20200301093659373" loading="lazy"></p>
<p>在上图中,由于H缺失了右子节点,所以它不是完全二叉树。</p>
<h4 id="23二叉树的数据存储">2.3.二叉树的数据存储</h4>
<p>常见的二叉树存储方式为<strong>数组</strong>和<strong>链表</strong>:</p>
<p><strong>使用数组:</strong></p>
<ul>
<li><strong>完全二叉树</strong>:按从上到下,从左到右的方式存储数据。</li>
</ul>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%A0%91%E4%B8%80/12.png" alt="image-20200301094919588" loading="lazy"></p>
<table>
<thead>
<tr>
<th>节点</th>
<th>A</th>
<th>B</th>
<th>C</th>
<th>D</th>
<th>E</th>
<th>F</th>
<th>G</th>
<th>H</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>序号</strong></td>
<td><strong>1</strong></td>
<td><strong>2</strong></td>
<td><strong>3</strong></td>
<td><strong>4</strong></td>
<td><strong>5</strong></td>
<td><strong>6</strong></td>
<td><strong>7</strong></td>
<td><strong>8</strong></td>
</tr>
</tbody>
</table>
<p>使用数组存储时,取数据的时候也十分方便:左子节点的序号等于父节点序号 * 2,右子节点的序号等于父节点序号 * 2 + 1 。</p>
<ul>
<li><strong>非完全二叉树</strong>:非完全二叉树需要转换成完全二叉树才能按照上面的方案存储,这样会浪费很大的存储空间。</li>
</ul>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%A0%91%E4%B8%80/13.png" alt="image-20200301100043636" loading="lazy"></p>
<table>
<thead>
<tr>
<th>节点</th>
<th>A</th>
<th>B</th>
<th>C</th>
<th>^</th>
<th>^</th>
<th>F</th>
<th>^</th>
<th>^</th>
<th>^</th>
<th>^</th>
<th>^</th>
<th>^</th>
<th>M</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>序号</strong></td>
<td><strong>1</strong></td>
<td><strong>2</strong></td>
<td><strong>3</strong></td>
<td><strong>4</strong></td>
<td><strong>5</strong></td>
<td><strong>6</strong></td>
<td><strong>7</strong></td>
<td><strong>8</strong></td>
<td><strong>9</strong></td>
<td><strong>10</strong></td>
<td><strong>11</strong></td>
<td><strong>12</strong></td>
<td><strong>13</strong></td>
</tr>
</tbody>
</table>
<p><strong>使用链表</strong></p>
<p>二叉树最常见的存储方式为<strong>链表</strong>:每一个节点封装成一个Node,Node中包含存储的数据、左节点的引用和右节点的引用。</p>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%A0%91%E4%B8%80/14.png" alt="image-20200301100616105" loading="lazy"></p>
<h3 id="三二叉搜索树">三、二叉搜索树</h3>
<h4 id="31认识二叉搜索树">3.1.认识二叉搜索树</h4>
<p><strong>二叉搜索树</strong>(<strong>BST</strong>,Binary Search Tree),也称为<strong>二叉排序树</strong>和<strong>二叉查找树</strong>。</p>
<p>二叉搜索树是一棵二叉树,可以为空;</p>
<p>如果不为空,则满足以下<strong>性质</strong>:</p>
<ul>
<li>条件1:非空左子树的<strong>所有</strong>键值<strong>小于</strong>其根节点的键值。比如三中节点6的所有非空左子树的键值都小于6;</li>
<li>条件2:非空右子树的<strong>所有</strong>键值<strong>大于</strong>其根节点的键值;比如三中节点6的所有非空右子树的键值都大于6;</li>
<li>条件3:左、右子树本身也都是二叉搜索树;</li>
</ul>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%A0%91%E4%B8%80/15.png" alt="image-20200301103139916" loading="lazy"></p>
<p>如上图所示,树二和树三符合3个条件属于二叉树,树一不满足条件3所以不是二叉树。</p>
<p><strong>总结:</strong>二叉搜索树的特点主要是<strong>较小的值</strong>总是保存在<strong>左节点</strong>上,相对<strong>较大的值</strong>总是保存在<strong>右节点</strong>上。这种特点使得二叉搜索树的查询效率非常高,这也就是二叉搜索树中"搜索"的来源。</p>
<h4 id="32二叉搜索树应用举例">3.2.二叉搜索树应用举例</h4>
<p>下面是一个二叉搜索树:</p>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%A0%91%E4%B8%80/16.png" alt="image-20200301111718686" loading="lazy"></p>
<p>若想在其中查找数据10,只需要查找4次,查找效率非常高。</p>
<ul>
<li>第1次:将10与根节点9进行比较,由于10 > 9,所以10下一步与根节点9的右子节点13比较;</li>
<li>第2次:由于10 < 13,所以10下一步与父节点13的左子节点11比较;</li>
<li>第3次:由于10 < 11,所以10下一步与父节点11的左子节点10比较;</li>
<li>第4次:由于10 = 10,最终查找到数据10 。</li>
</ul>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%A0%91%E4%B8%80/17.png" alt="image-20200301111751041" loading="lazy">同样是15个数据,在排序好的数组中查询数据10,需要查询10次:</p>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%A0%91%E4%B8%80/18.png" alt="image-20200301115348138" loading="lazy"></p>
<p>其实:如果是排序好的数组,可以通过二分查找:第一次找9,第二次找13,第三次找15...。我们发现如果把每次二分的数据拿出来以树的形式表示的话就是<strong>二叉搜索树</strong>。这就是数组二分法查找效率之所以高的原因。</p>
<blockquote>
<p>参考资料:JavaScript数据结构与算法</p>
</blockquote>
</div>
<div id="MySignature" role="contentinfo">
多抽出1分钟来学习,让你的生命更加精彩!<br><br>
来源:https://www.cnblogs.com/AhuntSun-blog/p/12446656.html
頁:
[1]