This commit is contained in:
krahets
2023-08-22 13:50:24 +08:00
parent 77b90cd19b
commit b70b7c9e75
67 changed files with 580 additions and 580 deletions

View File

@ -3617,13 +3617,13 @@
<h1 id="75-avl">7.5 &nbsp; AVL 树 *<a class="headerlink" href="#75-avl" title="Permanent link">&para;</a></h1>
<p>在二叉搜索树章节中,我们提到了在多次插入和删除操作后,二叉搜索树可能退化为链表。这种情况下,所有操作的时间复杂度将从 <span class="arithmatex">\(O(\log n)\)</span> 恶化为 <span class="arithmatex">\(O(n)\)</span></p>
<p>图所示,经过两次删除节点操作,这个二叉搜索树便会退化为链表。</p>
<p>如图 7-24 所示,经过两次删除节点操作,这个二叉搜索树便会退化为链表。</p>
<p><img alt="AVL 树在删除节点后发生退化" src="../avl_tree.assets/avltree_degradation_from_removing_node.png" /></p>
<p align="center">AVL 树在删除节点后发生退化 </p>
<p align="center"> 7-24 &nbsp; AVL 树在删除节点后发生退化 </p>
<p>再例如,在图的完美二叉树中插入两个节点后,树将严重向左倾斜,查找操作的时间复杂度也随之恶化。</p>
<p>再例如,在图 7-25 的完美二叉树中插入两个节点后,树将严重向左倾斜,查找操作的时间复杂度也随之恶化。</p>
<p><img alt="AVL 树在插入节点后发生退化" src="../avl_tree.assets/avltree_degradation_from_inserting_node.png" /></p>
<p align="center">AVL 树在插入节点后发生退化 </p>
<p align="center"> 7-25 &nbsp; AVL 树在插入节点后发生退化 </p>
<p>G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorithm for the organization of information" 中提出了「AVL 树」。论文中详细描述了一系列操作确保在持续添加和删除节点后AVL 树不会退化,从而使得各种操作的时间复杂度保持在 <span class="arithmatex">\(O(\log n)\)</span> 级别。换句话说在需要频繁进行增删查改操作的场景中AVL 树能始终保持高效的数据操作性能,具有很好的应用价值。</p>
<h2 id="751-avl">7.5.1 &nbsp; AVL 树常见术语<a class="headerlink" href="#751-avl" title="Permanent link">&para;</a></h2>
@ -4123,7 +4123,7 @@
<p>AVL 树的特点在于“旋转”操作,它能够在不影响二叉树的中序遍历序列的前提下,使失衡节点重新恢复平衡。换句话说,<strong>旋转操作既能保持“二叉搜索树”的性质,也能使树重新变为“平衡二叉树”</strong></p>
<p>我们将平衡因子绝对值 <span class="arithmatex">\(&gt; 1\)</span> 的节点称为“失衡节点”。根据节点失衡情况的不同,旋转操作分为四种:右旋、左旋、先右旋后左旋、先左旋后右旋。下面我们将详细介绍这些旋转操作。</p>
<h3 id="1_1">1. &nbsp; 右旋<a class="headerlink" href="#1_1" title="Permanent link">&para;</a></h3>
<p>图所示,节点下方为平衡因子。从底至顶看,二叉树中首个失衡节点是“节点 3”。我们关注以该失衡节点为根节点的子树将该节点记为 <code>node</code> ,其左子节点记为 <code>child</code> ,执行“右旋”操作。完成右旋后,子树已经恢复平衡,并且仍然保持二叉搜索树的特性。</p>
<p>如图 7-26 所示,节点下方为平衡因子。从底至顶看,二叉树中首个失衡节点是“节点 3”。我们关注以该失衡节点为根节点的子树将该节点记为 <code>node</code> ,其左子节点记为 <code>child</code> ,执行“右旋”操作。完成右旋后,子树已经恢复平衡,并且仍然保持二叉搜索树的特性。</p>
<div class="tabbed-set tabbed-alternate" data-tabs="4:4"><input checked="checked" id="__tabbed_4_1" name="__tabbed_4" type="radio" /><input id="__tabbed_4_2" name="__tabbed_4" type="radio" /><input id="__tabbed_4_3" name="__tabbed_4" type="radio" /><input id="__tabbed_4_4" name="__tabbed_4" type="radio" /><div class="tabbed-labels"><label for="__tabbed_4_1">&lt;1&gt;</label><label for="__tabbed_4_2">&lt;2&gt;</label><label for="__tabbed_4_3">&lt;3&gt;</label><label for="__tabbed_4_4">&lt;4&gt;</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
@ -4140,11 +4140,11 @@
</div>
</div>
</div>
<p align="center">右旋操作步骤 </p>
<p align="center"> 7-26 &nbsp; 右旋操作步骤 </p>
<p>图所示,当节点 <code>child</code> 有右子节点(记为 <code>grandChild</code> )时,需要在右旋中添加一步:将 <code>grandChild</code> 作为 <code>node</code> 的左子节点。</p>
<p>如图 7-27 所示,当节点 <code>child</code> 有右子节点(记为 <code>grandChild</code> )时,需要在右旋中添加一步:将 <code>grandChild</code> 作为 <code>node</code> 的左子节点。</p>
<p><img alt="有 grandChild 的右旋操作" src="../avl_tree.assets/avltree_right_rotate_with_grandchild.png" /></p>
<p align="center">有 grandChild 的右旋操作 </p>
<p align="center"> 7-27 &nbsp; 有 grandChild 的右旋操作 </p>
<p>“向右旋转”是一种形象化的说法,实际上需要通过修改节点指针来实现,代码如下所示。</p>
<div class="tabbed-set tabbed-alternate" data-tabs="5:12"><input checked="checked" id="__tabbed_5_1" name="__tabbed_5" type="radio" /><input id="__tabbed_5_2" name="__tabbed_5" type="radio" /><input id="__tabbed_5_3" name="__tabbed_5" type="radio" /><input id="__tabbed_5_4" name="__tabbed_5" type="radio" /><input id="__tabbed_5_5" name="__tabbed_5" type="radio" /><input id="__tabbed_5_6" name="__tabbed_5" type="radio" /><input id="__tabbed_5_7" name="__tabbed_5" type="radio" /><input id="__tabbed_5_8" name="__tabbed_5" type="radio" /><input id="__tabbed_5_9" name="__tabbed_5" type="radio" /><input id="__tabbed_5_10" name="__tabbed_5" type="radio" /><input id="__tabbed_5_11" name="__tabbed_5" type="radio" /><input id="__tabbed_5_12" name="__tabbed_5" type="radio" /><div class="tabbed-labels"><label for="__tabbed_5_1">Java</label><label for="__tabbed_5_2">C++</label><label for="__tabbed_5_3">Python</label><label for="__tabbed_5_4">Go</label><label for="__tabbed_5_5">JS</label><label for="__tabbed_5_6">TS</label><label for="__tabbed_5_7">C</label><label for="__tabbed_5_8">C#</label><label for="__tabbed_5_9">Swift</label><label for="__tabbed_5_10">Zig</label><label for="__tabbed_5_11">Dart</label><label for="__tabbed_5_12">Rust</label></div>
@ -4349,13 +4349,13 @@
</div>
</div>
<h3 id="2_1">2. &nbsp; 左旋<a class="headerlink" href="#2_1" title="Permanent link">&para;</a></h3>
<p>相应的,如果考虑上述失衡二叉树的“镜像”,则需要执行图所示的“左旋”操作。</p>
<p>相应的,如果考虑上述失衡二叉树的“镜像”,则需要执行图 7-28 所示的“左旋”操作。</p>
<p><img alt="左旋操作" src="../avl_tree.assets/avltree_left_rotate.png" /></p>
<p align="center">左旋操作 </p>
<p align="center"> 7-28 &nbsp; 左旋操作 </p>
<p>同理,如图所示,当节点 <code>child</code> 有左子节点(记为 <code>grandChild</code> )时,需要在左旋中添加一步:将 <code>grandChild</code> 作为 <code>node</code> 的右子节点。</p>
<p>同理,如图 7-29 所示,当节点 <code>child</code> 有左子节点(记为 <code>grandChild</code> )时,需要在左旋中添加一步:将 <code>grandChild</code> 作为 <code>node</code> 的右子节点。</p>
<p><img alt="有 grandChild 的左旋操作" src="../avl_tree.assets/avltree_left_rotate_with_grandchild.png" /></p>
<p align="center">有 grandChild 的左旋操作 </p>
<p align="center"> 7-29 &nbsp; 有 grandChild 的左旋操作 </p>
<p>可以观察到,<strong>右旋和左旋操作在逻辑上是镜像对称的,它们分别解决的两种失衡情况也是对称的</strong>。基于对称性,我们只需将右旋的实现代码中的所有的 <code>left</code> 替换为 <code>right</code> ,将所有的 <code>right</code> 替换为 <code>left</code> ,即可得到左旋的实现代码。</p>
<div class="tabbed-set tabbed-alternate" data-tabs="6:12"><input checked="checked" id="__tabbed_6_1" name="__tabbed_6" type="radio" /><input id="__tabbed_6_2" name="__tabbed_6" type="radio" /><input id="__tabbed_6_3" name="__tabbed_6" type="radio" /><input id="__tabbed_6_4" name="__tabbed_6" type="radio" /><input id="__tabbed_6_5" name="__tabbed_6" type="radio" /><input id="__tabbed_6_6" name="__tabbed_6" type="radio" /><input id="__tabbed_6_7" name="__tabbed_6" type="radio" /><input id="__tabbed_6_8" name="__tabbed_6" type="radio" /><input id="__tabbed_6_9" name="__tabbed_6" type="radio" /><input id="__tabbed_6_10" name="__tabbed_6" type="radio" /><input id="__tabbed_6_11" name="__tabbed_6" type="radio" /><input id="__tabbed_6_12" name="__tabbed_6" type="radio" /><div class="tabbed-labels"><label for="__tabbed_6_1">Java</label><label for="__tabbed_6_2">C++</label><label for="__tabbed_6_3">Python</label><label for="__tabbed_6_4">Go</label><label for="__tabbed_6_5">JS</label><label for="__tabbed_6_6">TS</label><label for="__tabbed_6_7">C</label><label for="__tabbed_6_8">C#</label><label for="__tabbed_6_9">Swift</label><label for="__tabbed_6_10">Zig</label><label for="__tabbed_6_11">Dart</label><label for="__tabbed_6_12">Rust</label></div>
@ -4560,22 +4560,22 @@
</div>
</div>
<h3 id="3">3. &nbsp; 先左旋后右旋<a class="headerlink" href="#3" title="Permanent link">&para;</a></h3>
<p>对于图中的失衡节点 3 ,仅使用左旋或右旋都无法使子树恢复平衡。此时需要先对 <code>child</code> 执行“左旋”,再对 <code>node</code> 执行“右旋”。</p>
<p>对于图 7-30 中的失衡节点 3 ,仅使用左旋或右旋都无法使子树恢复平衡。此时需要先对 <code>child</code> 执行“左旋”,再对 <code>node</code> 执行“右旋”。</p>
<p><img alt="先左旋后右旋" src="../avl_tree.assets/avltree_left_right_rotate.png" /></p>
<p align="center">先左旋后右旋 </p>
<p align="center"> 7-30 &nbsp; 先左旋后右旋 </p>
<h3 id="4">4. &nbsp; 先右旋后左旋<a class="headerlink" href="#4" title="Permanent link">&para;</a></h3>
<p>图所示,对于上述失衡二叉树的镜像情况,需要先对 <code>child</code> 执行“右旋”,然后对 <code>node</code> 执行“左旋”。</p>
<p>如图 7-31 所示,对于上述失衡二叉树的镜像情况,需要先对 <code>child</code> 执行“右旋”,然后对 <code>node</code> 执行“左旋”。</p>
<p><img alt="先右旋后左旋" src="../avl_tree.assets/avltree_right_left_rotate.png" /></p>
<p align="center">先右旋后左旋 </p>
<p align="center"> 7-31 &nbsp; 先右旋后左旋 </p>
<h3 id="5">5. &nbsp; 旋转的选择<a class="headerlink" href="#5" title="Permanent link">&para;</a></h3>
<p>图展示的四种失衡情况与上述案例逐个对应,分别需要采用右旋、左旋、先右后左、先左后右的旋转操作。</p>
<p> 7-32 展示的四种失衡情况与上述案例逐个对应,分别需要采用右旋、左旋、先右后左、先左后右的旋转操作。</p>
<p><img alt="AVL 树的四种旋转情况" src="../avl_tree.assets/avltree_rotation_cases.png" /></p>
<p align="center">AVL 树的四种旋转情况 </p>
<p align="center"> 7-32 &nbsp; AVL 树的四种旋转情况 </p>
<p>如下表所示,我们通过判断失衡节点的平衡因子以及较高一侧子节点的平衡因子的正负号,来确定失衡节点属于图中的哪种情况。</p>
<p align="center">四种旋转情况的选择条件 </p>
<p>如下表所示,我们通过判断失衡节点的平衡因子以及较高一侧子节点的平衡因子的正负号,来确定失衡节点属于图 7-32 中的哪种情况。</p>
<p align="center"> 7-3 &nbsp; 四种旋转情况的选择条件 </p>
<div class="center-table">
<table>