mirror of
https://github.com/krahets/hello-algo.git
synced 2026-07-15 16:36:06 +00:00
deploy
This commit is contained in:
@@ -1074,36 +1074,36 @@
|
||||
<nav class="md-nav" aria-label="7.4.2. AVL 树旋转">
|
||||
<ul class="md-nav__list">
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#case-1-" class="md-nav__link">
|
||||
Case 1 - 右旋
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#case-2-" class="md-nav__link">
|
||||
Case 2 - 左旋
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#case-3-" class="md-nav__link">
|
||||
Case 3 - 先左后右
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#case-4-" class="md-nav__link">
|
||||
Case 4 - 先右后左
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#_3" class="md-nav__link">
|
||||
右旋
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#_4" class="md-nav__link">
|
||||
左旋
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#_5" class="md-nav__link">
|
||||
先左旋后右旋
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#_6" class="md-nav__link">
|
||||
先右旋后左旋
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#_7" class="md-nav__link">
|
||||
旋转的选择
|
||||
</a>
|
||||
|
||||
@@ -1123,21 +1123,21 @@
|
||||
<ul class="md-nav__list">
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#_4" class="md-nav__link">
|
||||
<a href="#_8" class="md-nav__link">
|
||||
插入节点
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#_5" class="md-nav__link">
|
||||
<a href="#_9" class="md-nav__link">
|
||||
删除节点
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#_6" class="md-nav__link">
|
||||
<a href="#_10" class="md-nav__link">
|
||||
查找节点
|
||||
</a>
|
||||
|
||||
@@ -1839,36 +1839,36 @@
|
||||
<nav class="md-nav" aria-label="7.4.2. AVL 树旋转">
|
||||
<ul class="md-nav__list">
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#case-1-" class="md-nav__link">
|
||||
Case 1 - 右旋
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#case-2-" class="md-nav__link">
|
||||
Case 2 - 左旋
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#case-3-" class="md-nav__link">
|
||||
Case 3 - 先左后右
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#case-4-" class="md-nav__link">
|
||||
Case 4 - 先右后左
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#_3" class="md-nav__link">
|
||||
右旋
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#_4" class="md-nav__link">
|
||||
左旋
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#_5" class="md-nav__link">
|
||||
先左旋后右旋
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#_6" class="md-nav__link">
|
||||
先右旋后左旋
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#_7" class="md-nav__link">
|
||||
旋转的选择
|
||||
</a>
|
||||
|
||||
@@ -1888,21 +1888,21 @@
|
||||
<ul class="md-nav__list">
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#_4" class="md-nav__link">
|
||||
<a href="#_8" class="md-nav__link">
|
||||
插入节点
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#_5" class="md-nav__link">
|
||||
<a href="#_9" class="md-nav__link">
|
||||
删除节点
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#_6" class="md-nav__link">
|
||||
<a href="#_10" class="md-nav__link">
|
||||
查找节点
|
||||
</a>
|
||||
|
||||
@@ -1944,21 +1944,20 @@
|
||||
|
||||
|
||||
<h1 id="74-avl">7.4. AVL 树 *<a class="headerlink" href="#74-avl" title="Permanent link">¶</a></h1>
|
||||
<p>在「二叉搜索树」章节中提到,在进行多次插入与删除操作后,二叉搜索树可能会退化为链表。此时所有操作的时间复杂度都会由 <span class="arithmatex">\(O(\log n)\)</span> 劣化至 <span class="arithmatex">\(O(n)\)</span> 。</p>
|
||||
<p>如下图所示,执行两步删除节点后,该二叉搜索树就会退化为链表。</p>
|
||||
<p>在二叉搜索树章节中,我们提到了在多次插入和删除操作后,二叉搜索树可能退化为链表。这种情况下,所有操作的时间复杂度将从 <span class="arithmatex">\(O(\log n)\)</span> 恶化为 <span class="arithmatex">\(O(n)\)</span>。</p>
|
||||
<p>如下图所示,经过两次删除节点操作,这个二叉搜索树便会退化为链表。</p>
|
||||
<p><img alt="AVL 树在删除节点后发生退化" src="../avl_tree.assets/avltree_degradation_from_removing_node.png" /></p>
|
||||
<p align="center"> Fig. AVL 树在删除节点后发生退化 </p>
|
||||
|
||||
<p>再比如,在以下完美二叉树中插入两个节点后,树严重向左偏斜,查找操作的时间复杂度也随之发生劣化。</p>
|
||||
<p>再例如,在以下完美二叉树中插入两个节点后,树将严重向左倾斜,查找操作的时间复杂度也随之恶化。</p>
|
||||
<p><img alt="AVL 树在插入节点后发生退化" src="../avl_tree.assets/avltree_degradation_from_inserting_node.png" /></p>
|
||||
<p align="center"> Fig. AVL 树在插入节点后发生退化 </p>
|
||||
|
||||
<p>G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorithm for the organization of information" 中提出了「AVL 树」。<strong>论文中描述了一系列操作,使得在不断添加与删除节点后,AVL 树仍然不会发生退化</strong>,进而使得各种操作的时间复杂度均能保持在 <span class="arithmatex">\(O(\log n)\)</span> 级别。</p>
|
||||
<p>换言之,在频繁增删查改的使用场景中,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="741-avl">7.4.1. AVL 树常见术语<a class="headerlink" href="#741-avl" title="Permanent link">¶</a></h2>
|
||||
<p>「AVL 树」既是「二叉搜索树」又是「平衡二叉树」,同时满足这两种二叉树的所有性质,因此又被称为「平衡二叉搜索树」。</p>
|
||||
<p>「AVL 树」既是二叉搜索树也是平衡二叉树,同时满足这两类二叉树的所有性质,因此也被称为「平衡二叉搜索树」。</p>
|
||||
<h3 id="_1">节点高度<a class="headerlink" href="#_1" title="Permanent link">¶</a></h3>
|
||||
<p>在 AVL 树的操作中,需要获取节点「高度 Height」,所以给 AVL 树的节点类添加 <code>height</code> 变量。</p>
|
||||
<p>在操作 AVL 树时,我们需要获取节点的高度,因此需要为 AVL 树的节点类添加 <code>height</code> 变量。</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="1:10"><input checked="checked" id="__tabbed_1_1" name="__tabbed_1" type="radio" /><input id="__tabbed_1_2" name="__tabbed_1" type="radio" /><input id="__tabbed_1_3" name="__tabbed_1" type="radio" /><input id="__tabbed_1_4" name="__tabbed_1" type="radio" /><input id="__tabbed_1_5" name="__tabbed_1" type="radio" /><input id="__tabbed_1_6" name="__tabbed_1" type="radio" /><input id="__tabbed_1_7" name="__tabbed_1" type="radio" /><input id="__tabbed_1_8" name="__tabbed_1" type="radio" /><input id="__tabbed_1_9" name="__tabbed_1" type="radio" /><input id="__tabbed_1_10" name="__tabbed_1" type="radio" /><div class="tabbed-labels"><label for="__tabbed_1_1">Java</label><label for="__tabbed_1_2">C++</label><label for="__tabbed_1_3">Python</label><label for="__tabbed_1_4">Go</label><label for="__tabbed_1_5">JavaScript</label><label for="__tabbed_1_6">TypeScript</label><label for="__tabbed_1_7">C</label><label for="__tabbed_1_8">C#</label><label for="__tabbed_1_9">Swift</label><label for="__tabbed_1_10">Zig</label></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
@@ -2070,7 +2069,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>「节点高度」是最远叶节点到该节点的距离,即走过的「边」的数量。需要特别注意,<strong>叶节点的高度为 0 ,空节点的高度为 -1</strong>。我们封装两个工具函数,分别用于获取与更新节点的高度。</p>
|
||||
<p>「节点高度」是指从该节点到最远叶节点的距离,即所经过的“边”的数量。需要特别注意的是,叶节点的高度为 0 ,而空节点的高度为 -1 。我们将创建两个工具函数,分别用于获取和更新节点的高度。</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="2:10"><input checked="checked" id="__tabbed_2_1" name="__tabbed_2" type="radio" /><input id="__tabbed_2_2" name="__tabbed_2" type="radio" /><input id="__tabbed_2_3" name="__tabbed_2" type="radio" /><input id="__tabbed_2_4" name="__tabbed_2" type="radio" /><input id="__tabbed_2_5" name="__tabbed_2" type="radio" /><input id="__tabbed_2_6" name="__tabbed_2" type="radio" /><input id="__tabbed_2_7" name="__tabbed_2" type="radio" /><input id="__tabbed_2_8" name="__tabbed_2" type="radio" /><input id="__tabbed_2_9" name="__tabbed_2" type="radio" /><input id="__tabbed_2_10" name="__tabbed_2" type="radio" /><div class="tabbed-labels"><label for="__tabbed_2_1">Java</label><label for="__tabbed_2_2">C++</label><label for="__tabbed_2_3">Python</label><label for="__tabbed_2_4">Go</label><label for="__tabbed_2_5">JavaScript</label><label for="__tabbed_2_6">TypeScript</label><label for="__tabbed_2_7">C</label><label for="__tabbed_2_8">C#</label><label for="__tabbed_2_9">Swift</label><label for="__tabbed_2_10">Zig</label></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
@@ -2220,7 +2219,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="_2">节点平衡因子<a class="headerlink" href="#_2" title="Permanent link">¶</a></h3>
|
||||
<p>节点的「平衡因子 Balance Factor」是 <strong>节点的左子树高度减去右子树高度</strong>,并定义空节点的平衡因子为 0 。同样地,我们将获取节点平衡因子封装成函数,以便后续使用。</p>
|
||||
<p>节点的「平衡因子 Balance Factor」定义为节点左子树的高度减去右子树的高度,同时规定空节点的平衡因子为 0 。我们同样将获取节点平衡因子的功能封装成函数,方便后续使用。</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="3:10"><input checked="checked" id="__tabbed_3_1" name="__tabbed_3" type="radio" /><input id="__tabbed_3_2" name="__tabbed_3" type="radio" /><input id="__tabbed_3_3" name="__tabbed_3" type="radio" /><input id="__tabbed_3_4" name="__tabbed_3" type="radio" /><input id="__tabbed_3_5" name="__tabbed_3" type="radio" /><input id="__tabbed_3_6" name="__tabbed_3" type="radio" /><input id="__tabbed_3_7" name="__tabbed_3" type="radio" /><input id="__tabbed_3_8" name="__tabbed_3" type="radio" /><input id="__tabbed_3_9" name="__tabbed_3" type="radio" /><input id="__tabbed_3_10" name="__tabbed_3" type="radio" /><div class="tabbed-labels"><label for="__tabbed_3_1">Java</label><label for="__tabbed_3_2">C++</label><label for="__tabbed_3_3">Python</label><label for="__tabbed_3_4">Go</label><label for="__tabbed_3_5">JavaScript</label><label for="__tabbed_3_6">TypeScript</label><label for="__tabbed_3_7">C</label><label for="__tabbed_3_8">C#</label><label for="__tabbed_3_9">Swift</label><label for="__tabbed_3_10">Zig</label></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
@@ -2327,10 +2326,10 @@
|
||||
<p>设平衡因子为 <span class="arithmatex">\(f\)</span> ,则一棵 AVL 树的任意节点的平衡因子皆满足 <span class="arithmatex">\(-1 \le f \le 1\)</span> 。</p>
|
||||
</div>
|
||||
<h2 id="742-avl">7.4.2. AVL 树旋转<a class="headerlink" href="#742-avl" title="Permanent link">¶</a></h2>
|
||||
<p>AVL 树的独特之处在于「旋转 Rotation」的操作,其可 <strong>在不影响二叉树中序遍历序列的前提下,使失衡节点重新恢复平衡</strong>。换言之,旋转操作既可以使树保持为「二叉搜索树」,也可以使树重新恢复为「平衡二叉树」。</p>
|
||||
<p>我们将平衡因子的绝对值 <span class="arithmatex">\(> 1\)</span> 的节点称为「失衡节点」。根据节点的失衡情况,旋转操作分为 <strong>右旋、左旋、先右旋后左旋、先左旋后右旋</strong>,接下来我们来一起来看看它们是如何操作的。</p>
|
||||
<h3 id="case-1-">Case 1 - 右旋<a class="headerlink" href="#case-1-" title="Permanent link">¶</a></h3>
|
||||
<p>如下图所示(节点下方为「平衡因子」),从底至顶看,二叉树中首个失衡节点是 <strong>节点 3</strong>。我们聚焦在以该失衡节点为根节点的子树上,将该节点记为 <code>node</code> ,将其左子节点记为 <code>child</code> ,执行「右旋」操作。完成右旋后,该子树已经恢复平衡,并且仍然为二叉搜索树。</p>
|
||||
<p>AVL 树的特点在于「旋转 Rotation」操作,它能够在不影响二叉树的中序遍历序列的前提下,使失衡节点重新恢复平衡。换句话说,<strong>旋转操作既能保持树的「二叉搜索树」属性,也能使树重新变为「平衡二叉树」</strong>。</p>
|
||||
<p>我们将平衡因子绝对值 <span class="arithmatex">\(> 1\)</span> 的节点称为「失衡节点」。根据节点失衡情况的不同,旋转操作分为四种:右旋、左旋、先右旋后左旋、先左旋后右旋。下面我们将详细介绍这些旋转操作。</p>
|
||||
<h3 id="_3">右旋<a class="headerlink" href="#_3" title="Permanent link">¶</a></h3>
|
||||
<p>如下图所示,节点下方为平衡因子。从底至顶看,二叉树中首个失衡节点是“节点 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"><1></label><label for="__tabbed_4_2"><2></label><label for="__tabbed_4_3"><3></label><label for="__tabbed_4_4"><4></label></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
@@ -2347,11 +2346,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>进而,如果节点 <code>child</code> 本身有右子节点(记为 <code>grandChild</code> ),则需要在「右旋」中添加一步:将 <code>grandChild</code> 作为 <code>node</code> 的左子节点。</p>
|
||||
<p>此外,如果节点 <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"> Fig. 有 grandChild 的右旋操作 </p>
|
||||
|
||||
<p>“向右旋转”是一种形象化的说法,实际需要通过修改节点指针实现,代码如下所示。</p>
|
||||
<p>“向右旋转”是一种形象化的说法,实际上需要通过修改节点指针来实现,代码如下所示。</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="5:10"><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" /><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">JavaScript</label><label for="__tabbed_5_6">TypeScript</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></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
@@ -2504,8 +2503,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="case-2-">Case 2 - 左旋<a class="headerlink" href="#case-2-" title="Permanent link">¶</a></h3>
|
||||
<p>类似地,如果将取上述失衡二叉树的“镜像”,那么则需要「左旋」操作。</p>
|
||||
<h3 id="_4">左旋<a class="headerlink" href="#_4" title="Permanent link">¶</a></h3>
|
||||
<p>相应的,如果考虑上述失衡二叉树的“镜像”,则需要执行「左旋」操作。</p>
|
||||
<p><img alt="左旋操作" src="../avl_tree.assets/avltree_left_rotate.png" /></p>
|
||||
<p align="center"> Fig. 左旋操作 </p>
|
||||
|
||||
@@ -2513,7 +2512,7 @@
|
||||
<p><img alt="有 grandChild 的左旋操作" src="../avl_tree.assets/avltree_left_rotate_with_grandchild.png" /></p>
|
||||
<p align="center"> Fig. 有 grandChild 的左旋操作 </p>
|
||||
|
||||
<p>观察发现,<strong>「左旋」和「右旋」操作是镜像对称的,两者对应解决的两种失衡情况也是对称的</strong>。根据对称性,我们可以很方便地从「右旋」推导出「左旋」。具体地,只需将「右旋」代码中的把所有的 <code>left</code> 替换为 <code>right</code> 、所有的 <code>right</code> 替换为 <code>left</code> ,即可得到「左旋」代码。</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:10"><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" /><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">JavaScript</label><label for="__tabbed_6_6">TypeScript</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></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
@@ -2666,22 +2665,22 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="case-3-">Case 3 - 先左后右<a class="headerlink" href="#case-3-" title="Permanent link">¶</a></h3>
|
||||
<p>对于下图的失衡节点 3 ,<strong>单一使用左旋或右旋都无法使子树恢复平衡</strong>,此时需要「先左旋后右旋」,即先对 <code>child</code> 执行「左旋」,再对 <code>node</code> 执行「右旋」。</p>
|
||||
<h3 id="_5">先左旋后右旋<a class="headerlink" href="#_5" title="Permanent link">¶</a></h3>
|
||||
<p>对于下图中的失衡节点 3,仅使用左旋或右旋都无法使子树恢复平衡。此时需要先左旋后右旋,即先对 <code>child</code> 执行「左旋」,再对 <code>node</code> 执行「右旋」。</p>
|
||||
<p><img alt="先左旋后右旋" src="../avl_tree.assets/avltree_left_right_rotate.png" /></p>
|
||||
<p align="center"> Fig. 先左旋后右旋 </p>
|
||||
|
||||
<h3 id="case-4-">Case 4 - 先右后左<a class="headerlink" href="#case-4-" title="Permanent link">¶</a></h3>
|
||||
<p>同理,取以上失衡二叉树的镜像,则需要「先右旋后左旋」,即先对 <code>child</code> 执行「右旋」,然后对 <code>node</code> 执行「左旋」。</p>
|
||||
<h3 id="_6">先右旋后左旋<a class="headerlink" href="#_6" title="Permanent link">¶</a></h3>
|
||||
<p>同理,对于上述失衡二叉树的镜像情况,需要先右旋后左旋,即先对 <code>child</code> 执行「右旋」,然后对 <code>node</code> 执行「左旋」。</p>
|
||||
<p><img alt="先右旋后左旋" src="../avl_tree.assets/avltree_right_left_rotate.png" /></p>
|
||||
<p align="center"> Fig. 先右旋后左旋 </p>
|
||||
|
||||
<h3 id="_3">旋转的选择<a class="headerlink" href="#_3" title="Permanent link">¶</a></h3>
|
||||
<p>下图描述的四种失衡情况与上述 Cases 逐个对应,分别需采用 <strong>右旋、左旋、先右后左、先左后右</strong> 的旋转操作。</p>
|
||||
<h3 id="_7">旋转的选择<a class="headerlink" href="#_7" title="Permanent link">¶</a></h3>
|
||||
<p>下图展示的四种失衡情况与上述案例逐个对应,分别需要采用右旋、左旋、先右后左、先左后右的旋转操作。</p>
|
||||
<p><img alt="AVL 树的四种旋转情况" src="../avl_tree.assets/avltree_rotation_cases.png" /></p>
|
||||
<p align="center"> Fig. AVL 树的四种旋转情况 </p>
|
||||
|
||||
<p>具体地,在代码中使用 <strong>失衡节点的平衡因子、较高一侧子节点的平衡因子</strong> 来确定失衡节点属于上图中的哪种情况。</p>
|
||||
<p>在代码中,我们通过判断失衡节点的平衡因子以及较高一侧子节点的平衡因子的正负号,来确定失衡节点属于上图中的哪种情况。</p>
|
||||
<div class="center-table">
|
||||
<table>
|
||||
<thead>
|
||||
@@ -2715,7 +2714,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p>为方便使用,我们将旋转操作封装成一个函数。至此,<strong>我们可以使用此函数来旋转各种失衡情况,使失衡节点重新恢复平衡</strong>。</p>
|
||||
<p>为了便于使用,我们将旋转操作封装成一个函数。<strong>有了这个函数,我们就能对各种失衡情况进行旋转,使失衡节点重新恢复平衡</strong>。</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="7:10"><input checked="checked" id="__tabbed_7_1" name="__tabbed_7" type="radio" /><input id="__tabbed_7_2" name="__tabbed_7" type="radio" /><input id="__tabbed_7_3" name="__tabbed_7" type="radio" /><input id="__tabbed_7_4" name="__tabbed_7" type="radio" /><input id="__tabbed_7_5" name="__tabbed_7" type="radio" /><input id="__tabbed_7_6" name="__tabbed_7" type="radio" /><input id="__tabbed_7_7" name="__tabbed_7" type="radio" /><input id="__tabbed_7_8" name="__tabbed_7" type="radio" /><input id="__tabbed_7_9" name="__tabbed_7" type="radio" /><input id="__tabbed_7_10" name="__tabbed_7" type="radio" /><div class="tabbed-labels"><label for="__tabbed_7_1">Java</label><label for="__tabbed_7_2">C++</label><label for="__tabbed_7_3">Python</label><label for="__tabbed_7_4">Go</label><label for="__tabbed_7_5">JavaScript</label><label for="__tabbed_7_6">TypeScript</label><label for="__tabbed_7_7">C</label><label for="__tabbed_7_8">C#</label><label for="__tabbed_7_9">Swift</label><label for="__tabbed_7_10">Zig</label></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
@@ -3018,8 +3017,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<h2 id="743-avl">7.4.3. AVL 树常用操作<a class="headerlink" href="#743-avl" title="Permanent link">¶</a></h2>
|
||||
<h3 id="_4">插入节点<a class="headerlink" href="#_4" title="Permanent link">¶</a></h3>
|
||||
<p>「AVL 树」的节点插入操作与「二叉搜索树」主体类似。不同的是,在插入节点后,从该节点到根节点的路径上会出现一系列「失衡节点」。所以,<strong>我们需要从该节点开始,从底至顶地执行旋转操作,使所有失衡节点恢复平衡</strong>。</p>
|
||||
<h3 id="_8">插入节点<a class="headerlink" href="#_8" title="Permanent link">¶</a></h3>
|
||||
<p>「AVL 树」的节点插入操作与「二叉搜索树」在主体上类似。唯一的区别在于,在 AVL 树中插入节点后,从该节点到根节点的路径上可能会出现一系列失衡节点。因此,<strong>我们需要从这个节点开始,自底向上执行旋转操作,使所有失衡节点恢复平衡</strong>。</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="8:10"><input checked="checked" id="__tabbed_8_1" name="__tabbed_8" type="radio" /><input id="__tabbed_8_2" name="__tabbed_8" type="radio" /><input id="__tabbed_8_3" name="__tabbed_8" type="radio" /><input id="__tabbed_8_4" name="__tabbed_8" type="radio" /><input id="__tabbed_8_5" name="__tabbed_8" type="radio" /><input id="__tabbed_8_6" name="__tabbed_8" type="radio" /><input id="__tabbed_8_7" name="__tabbed_8" type="radio" /><input id="__tabbed_8_8" name="__tabbed_8" type="radio" /><input id="__tabbed_8_9" name="__tabbed_8" type="radio" /><input id="__tabbed_8_10" name="__tabbed_8" type="radio" /><div class="tabbed-labels"><label for="__tabbed_8_1">Java</label><label for="__tabbed_8_2">C++</label><label for="__tabbed_8_3">Python</label><label for="__tabbed_8_4">Go</label><label for="__tabbed_8_5">JavaScript</label><label for="__tabbed_8_6">TypeScript</label><label for="__tabbed_8_7">C</label><label for="__tabbed_8_8">C#</label><label for="__tabbed_8_9">Swift</label><label for="__tabbed_8_10">Zig</label></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
@@ -3271,8 +3270,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="_5">删除节点<a class="headerlink" href="#_5" title="Permanent link">¶</a></h3>
|
||||
<p>「AVL 树」删除节点操作与「二叉搜索树」删除节点操作总体相同。类似地,<strong>在删除节点后,也需要从底至顶地执行旋转操作,使所有失衡节点恢复平衡</strong>。</p>
|
||||
<h3 id="_9">删除节点<a class="headerlink" href="#_9" title="Permanent link">¶</a></h3>
|
||||
<p>类似地,在二叉搜索树的删除节点方法的基础上,需要从底至顶地执行旋转操作,使所有失衡节点恢复平衡。</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="9:10"><input checked="checked" id="__tabbed_9_1" name="__tabbed_9" type="radio" /><input id="__tabbed_9_2" name="__tabbed_9" type="radio" /><input id="__tabbed_9_3" name="__tabbed_9" type="radio" /><input id="__tabbed_9_4" name="__tabbed_9" type="radio" /><input id="__tabbed_9_5" name="__tabbed_9" type="radio" /><input id="__tabbed_9_6" name="__tabbed_9" type="radio" /><input id="__tabbed_9_7" name="__tabbed_9" type="radio" /><input id="__tabbed_9_8" name="__tabbed_9" type="radio" /><input id="__tabbed_9_9" name="__tabbed_9" type="radio" /><input id="__tabbed_9_10" name="__tabbed_9" type="radio" /><div class="tabbed-labels"><label for="__tabbed_9_1">Java</label><label for="__tabbed_9_2">C++</label><label for="__tabbed_9_3">Python</label><label for="__tabbed_9_4">Go</label><label for="__tabbed_9_5">JavaScript</label><label for="__tabbed_9_6">TypeScript</label><label for="__tabbed_9_7">C</label><label for="__tabbed_9_8">C#</label><label for="__tabbed_9_9">Swift</label><label for="__tabbed_9_10">Zig</label></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
@@ -3754,16 +3753,16 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="_6">查找节点<a class="headerlink" href="#_6" title="Permanent link">¶</a></h3>
|
||||
<p>「AVL 树」的节点查找操作与「二叉搜索树」一致,在此不再赘述。</p>
|
||||
<h3 id="_10">查找节点<a class="headerlink" href="#_10" title="Permanent link">¶</a></h3>
|
||||
<p>AVL 树的节点查找操作与二叉搜索树一致,在此不再赘述。</p>
|
||||
<h2 id="744-avl">7.4.4. AVL 树典型应用<a class="headerlink" href="#744-avl" title="Permanent link">¶</a></h2>
|
||||
<ul>
|
||||
<li>组织存储大型数据,适用于高频查找、低频增删场景;</li>
|
||||
<li>用于建立数据库中的索引系统;</li>
|
||||
<li>组织和存储大型数据,适用于高频查找、低频增删的场景;</li>
|
||||
<li>用于构建数据库中的索引系统;</li>
|
||||
</ul>
|
||||
<div class="admonition question">
|
||||
<p class="admonition-title">为什么红黑树比 AVL 树更受欢迎?</p>
|
||||
<p>红黑树的平衡条件相对宽松,因此在红黑树中插入与删除节点所需的旋转操作相对更少,节点增删操作相比 AVL 树的效率更高。</p>
|
||||
<p>红黑树的平衡条件相对宽松,因此在红黑树中插入与删除节点所需的旋转操作相对较少,在节点增删操作上的平均效率高于 AVL 树。</p>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user