deploy
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB |
|
@ -2275,7 +2275,7 @@
|
|||
</div>
|
||||
<div class="admonition question">
|
||||
<p class="admonition-title">尾节点指向什么?</p>
|
||||
<p>我们将链表的最后一个节点称为「尾节点」,其指向的是“空”,在 Java, C++, Python 中分别记为 <span class="arithmatex">\(\text{null}\)</span> , <span class="arithmatex">\(\text{nullptr}\)</span> , <span class="arithmatex">\(\text{None}\)</span> 。在不引起歧义的前提下,本书都使用 <span class="arithmatex">\(\text{null}\)</span> 来表示空。</p>
|
||||
<p>我们将链表的最后一个节点称为「尾节点」,其指向的是“空”,在 Java, C++, Python 中分别记为 <span class="arithmatex">\(\text{null}\)</span> , <span class="arithmatex">\(\text{nullptr}\)</span> , <span class="arithmatex">\(\text{None}\)</span> 。在不引起歧义的前提下,本书都使用 <span class="arithmatex">\(\text{None}\)</span> 来表示空。</p>
|
||||
</div>
|
||||
<div class="admonition question">
|
||||
<p class="admonition-title">如何称呼链表?</p>
|
||||
|
@ -3009,7 +3009,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<h2 id="424">4.2.4. 常见链表类型<a class="headerlink" href="#424" title="Permanent link">¶</a></h2>
|
||||
<p><strong>单向链表</strong>。即上述介绍的普通链表。单向链表的节点包含值和指向下一节点的指针(引用)两项数据。我们将首个节点称为头节点,将最后一个节点成为尾节点,尾节点指向 <span class="arithmatex">\(\text{null}\)</span> 。</p>
|
||||
<p><strong>单向链表</strong>。即上述介绍的普通链表。单向链表的节点包含值和指向下一节点的指针(引用)两项数据。我们将首个节点称为头节点,将最后一个节点成为尾节点,尾节点指向空 <span class="arithmatex">\(\text{None}\)</span> 。</p>
|
||||
<p><strong>环形链表</strong>。如果我们令单向链表的尾节点指向头节点(即首尾相接),则得到一个环形链表。在环形链表中,任意节点都可以视作头节点。</p>
|
||||
<p><strong>双向链表</strong>。与单向链表相比,双向链表记录了两个方向的指针(引用)。双向链表的节点定义同时包含指向后继节点(下一节点)和前驱节点(上一节点)的指针。相较于单向链表,双向链表更具灵活性,可以朝两个方向遍历链表,但相应地也需要占用更多的内存空间。</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="7:11"><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" /><input id="__tabbed_7_11" 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><label for="__tabbed_7_11">Dart</label></div>
|
||||
|
|
|
@ -2148,11 +2148,14 @@
|
|||
<p>为了获得最佳的阅读体验,建议您通读本节内容。</p>
|
||||
</div>
|
||||
<h2 id="021">0.2.1. 行文风格约定<a class="headerlink" href="#021" title="Permanent link">¶</a></h2>
|
||||
<p>标题后标注 <code>*</code> 的是选读章节,内容相对困难。如果你的时间有限,建议可以先跳过。</p>
|
||||
<p>文章中的重要名词会用 <code>「 」</code> 括号标注,例如 <code>「数组 Array」</code> 。请务必记住这些名词,包括英文翻译,以便后续阅读文献时使用。</p>
|
||||
<p><strong>加粗的文字</strong> 表示重点内容或总结性语句,这类文字值得特别关注。</p>
|
||||
<p>专有名词和有特指含义的词句会使用 <code>“双引号”</code> 标注,以避免歧义。</p>
|
||||
<p>本书部分放弃了编程语言的注释规范,以换取更加紧凑的内容排版。注释主要分为三种类型:标题注释、内容注释、多行注释。</p>
|
||||
<ul>
|
||||
<li>标题后标注 <code>*</code> 的是选读章节,内容相对困难。如果你的时间有限,建议可以先跳过。</li>
|
||||
<li>文章中的重要名词会用 <code>「 」</code> 括号标注,例如 <code>「数组 Array」</code> 。请务必记住这些名词,包括英文翻译,以便后续阅读文献时使用。</li>
|
||||
<li><strong>加粗的文字</strong> 表示重点内容或总结性语句,这类文字值得特别关注。</li>
|
||||
<li>专有名词和有特指含义的词句会使用 <code>“双引号”</code> 标注,以避免歧义。</li>
|
||||
<li>涉及到编程语言之间不一致的名词,本书均以 Python 为准,例如使用 <span class="arithmatex">\(\text{None}\)</span> 来表示“空”。</li>
|
||||
<li>本书部分放弃了编程语言的注释规范,以换取更加紧凑的内容排版。注释主要分为三种类型:标题注释、内容注释、多行注释。</li>
|
||||
</ul>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="1:11"><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" /><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><label for="__tabbed_1_11">Dart</label></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
|
|
After Width: | Height: | Size: 86 KiB |
After Width: | Height: | Size: 67 KiB |
After Width: | Height: | Size: 75 KiB |
After Width: | Height: | Size: 66 KiB |
|
@ -2120,16 +2120,16 @@
|
|||
<h2 id="731">7.3.1. 表示完美二叉树<a class="headerlink" href="#731" title="Permanent link">¶</a></h2>
|
||||
<p>先分析一个简单案例,给定一个完美二叉树,我们将节点按照层序遍历的顺序编号(从 <span class="arithmatex">\(0\)</span> 开始),此时每个节点都对应唯一的索引。</p>
|
||||
<p>根据层序遍历的特性,我们可以推导出父节点索引与子节点索引之间的“映射公式”:<strong>若节点的索引为 <span class="arithmatex">\(i\)</span> ,则该节点的左子节点索引为 <span class="arithmatex">\(2i + 1\)</span> ,右子节点索引为 <span class="arithmatex">\(2i + 2\)</span></strong> 。</p>
|
||||
<p><img alt="完美二叉树的数组表示" src="../binary_tree.assets/array_representation_mapping.png" /></p>
|
||||
<p><img alt="完美二叉树的数组表示" src="../array_representation_of_tree.assets/array_representation_binary_tree.png" /></p>
|
||||
<p align="center"> Fig. 完美二叉树的数组表示 </p>
|
||||
|
||||
<p><strong>映射公式的作用相当于链表中的指针</strong>。如果我们将节点按照层序遍历的顺序存储在一个数组中,那么对于数组中的任意节点,我们都可以通过映射公式来访问其子节点。</p>
|
||||
<h2 id="732">7.3.2. 表示任意二叉树<a class="headerlink" href="#732" title="Permanent link">¶</a></h2>
|
||||
<p>然而,完美二叉树只是一个特例。在二叉树的中间层,通常存在许多 <span class="arithmatex">\(\text{null}\)</span> ,而层序遍历序列并不包含这些 <span class="arithmatex">\(\text{null}\)</span> 。我们无法仅凭该序列来推测 <span class="arithmatex">\(\text{null}\)</span> 的数量和分布位置,<strong>这意味着存在多种二叉树结构都符合该层序遍历序列</strong>。显然在这种情况下,上述的数组表示方法已经失效。</p>
|
||||
<p><img alt="层序遍历序列对应多种二叉树可能性" src="../binary_tree.assets/array_representation_without_empty.png" /></p>
|
||||
<p>然而,完美二叉树只是一个特例。在二叉树的中间层,通常存在许多 <span class="arithmatex">\(\text{None}\)</span> ,而层序遍历序列并不包含这些 <span class="arithmatex">\(\text{None}\)</span> 。我们无法仅凭该序列来推测 <span class="arithmatex">\(\text{None}\)</span> 的数量和分布位置,<strong>这意味着存在多种二叉树结构都符合该层序遍历序列</strong>。显然在这种情况下,上述的数组表示方法已经失效。</p>
|
||||
<p><img alt="层序遍历序列对应多种二叉树可能性" src="../array_representation_of_tree.assets/array_representation_without_empty.png" /></p>
|
||||
<p align="center"> Fig. 层序遍历序列对应多种二叉树可能性 </p>
|
||||
|
||||
<p>为了解决此问题,<strong>我们可以考虑在层序遍历序列中显式地写出所有 <span class="arithmatex">\(\text{null}\)</span></strong>。如下图所示,这样处理后,层序遍历序列就可以唯一表示二叉树了。</p>
|
||||
<p>为了解决此问题,<strong>我们可以考虑在层序遍历序列中显式地写出所有 <span class="arithmatex">\(\text{None}\)</span></strong>。如下图所示,这样处理后,层序遍历序列就可以唯一表示二叉树了。</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="1:11"><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" /><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><label for="__tabbed_1_11">Dart</label></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
|
@ -2198,7 +2198,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p><img alt="任意类型二叉树的数组表示" src="../binary_tree.assets/array_representation_with_empty.png" /></p>
|
||||
<p><img alt="任意类型二叉树的数组表示" src="../array_representation_of_tree.assets/array_representation_with_empty.png" /></p>
|
||||
<p align="center"> Fig. 任意类型二叉树的数组表示 </p>
|
||||
|
||||
<h2 id="733">7.3.3. 优势与局限性<a class="headerlink" href="#733" title="Permanent link">¶</a></h2>
|
||||
|
@ -2212,10 +2212,10 @@
|
|||
<ul>
|
||||
<li>数组存储需要连续内存空间,因此不适合存储数据量过大的树。</li>
|
||||
<li>增删节点需要通过数组插入与删除操作实现,效率较低;</li>
|
||||
<li>当二叉树中存在大量 <span class="arithmatex">\(\text{null}\)</span> 时,数组中包含的节点数据比重较低,空间利用率较低。</li>
|
||||
<li>当二叉树中存在大量 <span class="arithmatex">\(\text{None}\)</span> 时,数组中包含的节点数据比重较低,空间利用率较低。</li>
|
||||
</ul>
|
||||
<p><strong>完全二叉树非常适合使用数组来表示</strong>。回顾完全二叉树的定义,<span class="arithmatex">\(\text{null}\)</span> 只出现在最底层且靠右的位置,<strong>这意味着所有 <span class="arithmatex">\(\text{null}\)</span> 一定出现在层序遍历序列的末尾</strong>。因此,在使用数组表示完全二叉树时,可以省略存储所有 <span class="arithmatex">\(\text{null}\)</span> 。</p>
|
||||
<p><img alt="完全二叉树的数组表示" src="../binary_tree.assets/array_representation_complete_binary_tree.png" /></p>
|
||||
<p><strong>完全二叉树非常适合使用数组来表示</strong>。回顾完全二叉树的定义,<span class="arithmatex">\(\text{None}\)</span> 只出现在最底层且靠右的位置,<strong>这意味着所有 <span class="arithmatex">\(\text{None}\)</span> 一定出现在层序遍历序列的末尾</strong>。因此,在使用数组表示完全二叉树时,可以省略存储所有 <span class="arithmatex">\(\text{None}\)</span> 。</p>
|
||||
<p><img alt="完全二叉树的数组表示" src="../array_representation_of_tree.assets/array_representation_complete_binary_tree.png" /></p>
|
||||
<p align="center"> Fig. 完全二叉树的数组表示 </p>
|
||||
|
||||
|
||||
|
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 86 KiB |
|
@ -2435,8 +2435,8 @@
|
|||
<h3 id="_2">插入节点<a class="headerlink" href="#_2" title="Permanent link">¶</a></h3>
|
||||
<p>给定一个待插入元素 <code>num</code> ,为了保持二叉搜索树“左子树 < 根节点 < 右子树”的性质,插入操作分为两步:</p>
|
||||
<ol>
|
||||
<li><strong>查找插入位置</strong>:与查找操作相似,从根节点出发,根据当前节点值和 <code>num</code> 的大小关系循环向下搜索,直到越过叶节点(遍历至 <span class="arithmatex">\(\text{null}\)</span> )时跳出循环;</li>
|
||||
<li><strong>在该位置插入节点</strong>:初始化节点 <code>num</code> ,将该节点置于 <span class="arithmatex">\(\text{null}\)</span> 的位置;</li>
|
||||
<li><strong>查找插入位置</strong>:与查找操作相似,从根节点出发,根据当前节点值和 <code>num</code> 的大小关系循环向下搜索,直到越过叶节点(遍历至 <span class="arithmatex">\(\text{None}\)</span> )时跳出循环;</li>
|
||||
<li><strong>在该位置插入节点</strong>:初始化节点 <code>num</code> ,将该节点置于 <span class="arithmatex">\(\text{None}\)</span> 的位置;</li>
|
||||
</ol>
|
||||
<p>二叉搜索树不允许存在重复节点,否则将违反其定义。因此,若待插入节点在树中已存在,则不执行插入,直接返回。</p>
|
||||
<p><img alt="在二叉搜索树中插入节点" src="../binary_search_tree.assets/bst_insert.png" /></p>
|
||||
|
@ -2754,7 +2754,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>为了插入节点,我们需要利用辅助节点 <code>pre</code> 保存上一轮循环的节点,这样在遍历至 <span class="arithmatex">\(\text{null}\)</span> 时,我们可以获取到其父节点,从而完成节点插入操作。</p>
|
||||
<p>为了插入节点,我们需要利用辅助节点 <code>pre</code> 保存上一轮循环的节点,这样在遍历至 <span class="arithmatex">\(\text{None}\)</span> 时,我们可以获取到其父节点,从而完成节点插入操作。</p>
|
||||
<p>与查找节点相同,插入节点使用 <span class="arithmatex">\(O(\log n)\)</span> 时间。</p>
|
||||
<h3 id="_3">删除节点<a class="headerlink" href="#_3" title="Permanent link">¶</a></h3>
|
||||
<p>与插入节点类似,我们需要在删除操作后维持二叉搜索树的“左子树 < 根节点 < 右子树”的性质。首先,我们需要在二叉树中执行查找操作,获取待删除节点。接下来,根据待删除节点的子节点数量,删除操作需分为三种情况:</p>
|
||||
|
|
Before Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 91 KiB |
Before Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
BIN
chapter_tree/binary_tree.assets/binary_tree_best_worst_cases.png
Normal file
After Width: | Height: | Size: 63 KiB |
Before Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 59 KiB |
|
@ -2342,7 +2342,7 @@
|
|||
<p>二叉树涉及的术语较多,建议尽量理解并记住。</p>
|
||||
<ul>
|
||||
<li>「根节点 Root Node」:位于二叉树顶层的节点,没有父节点;</li>
|
||||
<li>「叶节点 Leaf Node」:没有子节点的节点,其两个指针均指向 <span class="arithmatex">\(\text{null}\)</span> ;</li>
|
||||
<li>「叶节点 Leaf Node」:没有子节点的节点,其两个指针均指向 <span class="arithmatex">\(\text{None}\)</span> ;</li>
|
||||
<li>节点的「层 Level」:从顶至底递增,根节点所在层为 1 ;</li>
|
||||
<li>节点的「度 Degree」:节点的子节点的数量。在二叉树中,度的范围是 0, 1, 2 ;</li>
|
||||
<li>「边 Edge」:连接两个节点的线段,即节点指针;</li>
|
||||
|
@ -2660,7 +2660,7 @@
|
|||
<li>完美二叉树是理想情况,可以充分发挥二叉树“分治”的优势;</li>
|
||||
<li>链表则是另一个极端,各项操作都变为线性操作,时间复杂度退化至 <span class="arithmatex">\(O(n)\)</span> ;</li>
|
||||
</ul>
|
||||
<p><img alt="二叉树的最佳与最差结构" src="../binary_tree.assets/binary_tree_corner_cases.png" /></p>
|
||||
<p><img alt="二叉树的最佳与最差结构" src="../binary_tree.assets/binary_tree_best_worst_cases.png" /></p>
|
||||
<p align="center"> Fig. 二叉树的最佳与最差结构 </p>
|
||||
|
||||
<p>如下表所示,在最佳和最差结构下,二叉树的叶节点数量、节点总数、高度等达到极大或极小值。</p>
|
||||
|
|