This commit is contained in:
krahets
2023-08-29 20:57:26 +08:00
parent 6ae6c480e0
commit 8c4d24795c
32 changed files with 574 additions and 278 deletions

View File

@@ -5904,11 +5904,8 @@
<ul>
<li>组织和存储大型数据,适用于高频查找、低频增删的场景。</li>
<li>用于构建数据库中的索引系统。</li>
<li>红黑树在许多应用中比 AVL 树更受欢迎。这是因为红黑树的平衡条件相对宽松,在红黑树中插入与删除节点所需的旋转操作相对较少,其节点增删操作的平均效率更高。</li>
</ul>
<div class="admonition question">
<p class="admonition-title">为什么红黑树比 AVL 树更受欢迎?</p>
<p>红黑树的平衡条件相对宽松,因此在红黑树中插入与删除节点所需的旋转操作相对较少,在节点增删操作上的平均效率高于 AVL 树。</p>
</div>

View File

@@ -4206,11 +4206,9 @@
</div>
<p>与查找节点相同,插入节点使用 <span class="arithmatex">\(O(\log n)\)</span> 时间。</p>
<h3 id="3">3. &nbsp; 删除节点<a class="headerlink" href="#3" title="Permanent link">&para;</a></h3>
<p>先在二叉树中查找到目标节点,再将其从二叉树中删除。</p>
<p>与插入节点类似,我们需要保证在删除操作完成后,二叉搜索树的“左子树 &lt; 根节点 &lt; 右子树”的性质仍然满足。</p>
<ol>
<li>在二叉树中执行查找操作,获取待删除节点。</li>
<li>根据待删除节点的子节点数量(三种情况),执行对应的删除节点操作。</li>
</ol>
<p>因此,我们需要根据目标节点的子节点数量,共分为 0、1 和 2 这三种情况,执行对应的删除节点操作。</p>
<p>如图 7-19 所示,当待删除节点的度为 <span class="arithmatex">\(0\)</span> 时,表示该节点是叶节点,可以直接删除。</p>
<p><img alt="在二叉搜索树中删除节点(度为 0 " src="../binary_search_tree.assets/bst_remove_case1.png" /></p>
<p align="center"> 图 7-19 &nbsp; 在二叉搜索树中删除节点(度为 0 </p>

View File

@@ -3739,7 +3739,7 @@
<p align="center"> 图 7-2 &nbsp; 二叉树的常用术语 </p>
<div class="admonition tip">
<p class="admonition-title">高度与深度的定义</p>
<p class="admonition-title">Tip</p>
<p>请注意,我们通常将“高度”和“深度”定义为“走过边的数量”,但有些题目或教材可能会将其定义为“走过节点的数量”。在这种情况下,高度和深度都需要加 1 。</p>
</div>
<h2 id="712">7.1.2 &nbsp; 二叉树基本操作<a class="headerlink" href="#712" title="Permanent link">&para;</a></h2>

View File

@@ -1435,6 +1435,26 @@
7.2.1 &nbsp; 层序遍历
</a>
<nav class="md-nav" aria-label="7.2.1   层序遍历">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#1" class="md-nav__link">
1. &nbsp; 代码实现
</a>
</li>
<li class="md-nav__item">
<a href="#2" class="md-nav__link">
2. &nbsp; 复杂度分析
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
@@ -1442,6 +1462,26 @@
7.2.2 &nbsp; 前序、中序、后序遍历
</a>
<nav class="md-nav" aria-label="7.2.2   前序、中序、后序遍历">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#1_1" class="md-nav__link">
1. &nbsp; 代码实现
</a>
</li>
<li class="md-nav__item">
<a href="#2_1" class="md-nav__link">
2. &nbsp; 复杂度分析
</a>
</li>
</ul>
</nav>
</li>
</ul>
@@ -3409,6 +3449,26 @@
7.2.1 &nbsp; 层序遍历
</a>
<nav class="md-nav" aria-label="7.2.1   层序遍历">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#1" class="md-nav__link">
1. &nbsp; 代码实现
</a>
</li>
<li class="md-nav__item">
<a href="#2" class="md-nav__link">
2. &nbsp; 复杂度分析
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
@@ -3416,6 +3476,26 @@
7.2.2 &nbsp; 前序、中序、后序遍历
</a>
<nav class="md-nav" aria-label="7.2.2   前序、中序、后序遍历">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#1_1" class="md-nav__link">
1. &nbsp; 代码实现
</a>
</li>
<li class="md-nav__item">
<a href="#2_1" class="md-nav__link">
2. &nbsp; 复杂度分析
</a>
</li>
</ul>
</nav>
</li>
</ul>
@@ -3450,6 +3530,7 @@
<p><img alt="二叉树的层序遍历" src="../binary_tree_traversal.assets/binary_tree_bfs.png" /></p>
<p align="center"> 图 7-9 &nbsp; 二叉树的层序遍历 </p>
<h3 id="1">1. &nbsp; 代码实现<a class="headerlink" href="#1" title="Permanent link">&para;</a></h3>
<p>广度优先遍历通常借助“队列”来实现。队列遵循“先进先出”的规则,而广度优先遍历则遵循“逐层推进”的规则,两者背后的思想是一致的。</p>
<div class="tabbed-set tabbed-alternate" data-tabs="1:12"><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" /><input id="__tabbed_1_11" name="__tabbed_1" type="radio" /><input id="__tabbed_1_12" 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">JS</label><label for="__tabbed_1_6">TS</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><label for="__tabbed_1_11">Dart</label><label for="__tabbed_1_12">Rust</label></div>
<div class="tabbed-content">
@@ -3733,14 +3814,19 @@
</div>
</div>
</div>
<p><strong>时间复杂度</strong>:所有节点被访问一次,使用 <span class="arithmatex">\(O(n)\)</span> 时间,其中 <span class="arithmatex">\(n\)</span> 为节点数量。</p>
<p><strong>空间复杂度</strong>:在最差情况下,即满二叉树时,遍历到最底层之前,队列中最多同时存在 <span class="arithmatex">\((n + 1) / 2\)</span> 个节点,占用 <span class="arithmatex">\(O(n)\)</span> 空间。</p>
<h3 id="2">2. &nbsp; 复杂度分析<a class="headerlink" href="#2" title="Permanent link">&para;</a></h3>
<ul>
<li><strong>时间复杂度 <span class="arithmatex">\(O(n)\)</span></strong> :所有节点被访问一次,使用 <span class="arithmatex">\(O(n)\)</span> 时间,其中 <span class="arithmatex">\(n\)</span> 为节点数量。</li>
<li><strong>空间复杂度 <span class="arithmatex">\(O(n)\)</span></strong> :在最差情况下,即满二叉树时,遍历到最底层之前,队列中最多同时存在 <span class="arithmatex">\((n + 1) / 2\)</span> 个节点,占用 <span class="arithmatex">\(O(n)\)</span> 空间。</li>
</ul>
<h2 id="722">7.2.2 &nbsp; 前序、中序、后序遍历<a class="headerlink" href="#722" title="Permanent link">&para;</a></h2>
<p>相应地,前序、中序和后序遍历都属于「深度优先遍历 depth-first traversal」它体现了一种“先走到尽头再回溯继续”的遍历方式。</p>
<p>图 7-10 展示了对二叉树进行深度优先遍历的工作原理。<strong>深度优先遍历就像是绕着整个二叉树的外围“走”一圈</strong>,在每个节点都会遇到三个位置,分别对应前序遍历、中序遍历和后序遍历。</p>
<p><img alt="二叉搜索树的前、中、后序遍历" src="../binary_tree_traversal.assets/binary_tree_dfs.png" /></p>
<p align="center"> 图 7-10 &nbsp; 二叉搜索树的前、中、后序遍历 </p>
<h3 id="1_1">1. &nbsp; 代码实现<a class="headerlink" href="#1_1" title="Permanent link">&para;</a></h3>
<p>深度优先搜索通常基于递归实现:</p>
<div class="tabbed-set tabbed-alternate" data-tabs="2:12"><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" /><input id="__tabbed_2_11" name="__tabbed_2" type="radio" /><input id="__tabbed_2_12" 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">JS</label><label for="__tabbed_2_6">TS</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><label for="__tabbed_2_11">Dart</label><label for="__tabbed_2_12">Rust</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
@@ -4132,11 +4218,9 @@
</div>
</div>
</div>
<p><strong>时间复杂度</strong>:所有节点被访问一次,使用 <span class="arithmatex">\(O(n)\)</span> 时间,其中 <span class="arithmatex">\(n\)</span> 为节点数量。</p>
<p><strong>空间复杂度</strong>:在最差情况下,即树退化为链表时,递归深度达到 <span class="arithmatex">\(n\)</span> ,系统占用 <span class="arithmatex">\(O(n)\)</span> 栈帧空间。</p>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>我们也可以不使用递归,仅基于迭代实现前、中、后序遍历,有兴趣的同学可以自行实现</p>
<p>深度优先搜索也可以基于迭代实现,有兴趣的同学可以自行研究</p>
</div>
<p>图 7-11 展示了前序遍历二叉树的递归过程,其可分为“递”和“归”两个逆向的部分。</p>
<ol>
@@ -4182,6 +4266,12 @@
</div>
<p align="center"> 图 7-11 &nbsp; 前序遍历的递归过程 </p>
<h3 id="2_1">2. &nbsp; 复杂度分析<a class="headerlink" href="#2_1" title="Permanent link">&para;</a></h3>
<ul>
<li><strong>时间复杂度 <span class="arithmatex">\(O(n)\)</span></strong> :所有节点被访问一次,使用 <span class="arithmatex">\(O(n)\)</span> 时间。</li>
<li><strong>空间复杂度 <span class="arithmatex">\(O(n)\)</span></strong> :在最差情况下,即树退化为链表时,递归深度达到 <span class="arithmatex">\(n\)</span> ,系统占用 <span class="arithmatex">\(O(n)\)</span> 栈帧空间。</li>
</ul>

View File

@@ -1511,8 +1511,15 @@
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#761-q-a" class="md-nav__link">
7.6.1 &nbsp; Q &amp; A
<a href="#1" class="md-nav__link">
1. &nbsp; 重点回顾
</a>
</li>
<li class="md-nav__item">
<a href="#2-q-a" class="md-nav__link">
2. &nbsp; Q &amp; A
</a>
</li>
@@ -3398,8 +3405,15 @@
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#761-q-a" class="md-nav__link">
7.6.1 &nbsp; Q &amp; A
<a href="#1" class="md-nav__link">
1. &nbsp; 重点回顾
</a>
</li>
<li class="md-nav__item">
<a href="#2-q-a" class="md-nav__link">
2. &nbsp; Q &amp; A
</a>
</li>
@@ -3428,6 +3442,7 @@
<h1 id="76">7.6 &nbsp; 小结<a class="headerlink" href="#76" title="Permanent link">&para;</a></h1>
<h3 id="1">1. &nbsp; 重点回顾<a class="headerlink" href="#1" title="Permanent link">&para;</a></h3>
<ul>
<li>二叉树是一种非线性数据结构,体现“一分为二”的分治逻辑。每个二叉树节点包含一个值以及两个指针,分别指向其左子节点和右子节点。</li>
<li>对于二叉树中的某个节点,其左(右)子节点及其以下形成的树被称为该节点的左(右)子树。</li>
@@ -3441,7 +3456,7 @@
<li>AVL 树,也称为平衡二叉搜索树,它通过旋转操作,确保在不断插入和删除节点后,树仍然保持平衡。</li>
<li>AVL 树的旋转操作包括右旋、左旋、先右旋再左旋、先左旋再右旋。在插入或删除节点后AVL 树会从底向顶执行旋转操作,使树重新恢复平衡。</li>
</ul>
<h2 id="761-q-a">7.6.1 &nbsp; Q &amp; A<a class="headerlink" href="#761-q-a" title="Permanent link">&para;</a></h2>
<h3 id="2-q-a">2. &nbsp; Q &amp; A<a class="headerlink" href="#2-q-a" title="Permanent link">&para;</a></h3>
<div class="admonition question">
<p class="admonition-title">对于只有一个节点的二叉树,树的高度和根节点的深度都是 <span class="arithmatex">\(0\)</span> 吗?</p>
<p>是的,因为高度和深度通常定义为“走过边的数量”。</p>