mirror of
https://github.com/krahets/hello-algo.git
synced 2024-12-26 15:16:29 +08:00
deploy
This commit is contained in:
parent
62d7f2c85d
commit
d5d3a29676
31 changed files with 179 additions and 1 deletions
|
@ -1602,6 +1602,8 @@
|
||||||
<h1 id="41">4.1. 数组<a class="headerlink" href="#41" title="Permanent link">¶</a></h1>
|
<h1 id="41">4.1. 数组<a class="headerlink" href="#41" title="Permanent link">¶</a></h1>
|
||||||
<p>「数组 Array」是一种将 <strong>相同类型元素</strong> 存储在 <strong>连续内存空间</strong> 的数据结构,将元素在数组中的位置称为元素的「索引 Index」。</p>
|
<p>「数组 Array」是一种将 <strong>相同类型元素</strong> 存储在 <strong>连续内存空间</strong> 的数据结构,将元素在数组中的位置称为元素的「索引 Index」。</p>
|
||||||
<p><img alt="数组定义与存储方式" src="../array.assets/array_definition.png" /></p>
|
<p><img alt="数组定义与存储方式" src="../array.assets/array_definition.png" /></p>
|
||||||
|
<p align="center"> Fig. 数组定义与存储方式 </p>
|
||||||
|
|
||||||
<div class="admonition note">
|
<div class="admonition note">
|
||||||
<p class="admonition-title">Note</p>
|
<p class="admonition-title">Note</p>
|
||||||
<p>观察上图,我们发现 <strong>数组首元素的索引为 <span class="arithmatex">\(0\)</span></strong> 。你可能会想,这并不符合日常习惯,首个元素的索引为什么不是 <span class="arithmatex">\(1\)</span> 呢,这不是更加自然吗?我认同你的想法,但请先记住这个设定,后面讲内存地址计算时,我会尝试解答这个问题。</p>
|
<p>观察上图,我们发现 <strong>数组首元素的索引为 <span class="arithmatex">\(0\)</span></strong> 。你可能会想,这并不符合日常习惯,首个元素的索引为什么不是 <span class="arithmatex">\(1\)</span> 呢,这不是更加自然吗?我认同你的想法,但请先记住这个设定,后面讲内存地址计算时,我会尝试解答这个问题。</p>
|
||||||
|
@ -1680,6 +1682,8 @@
|
||||||
<h2 id="411">4.1.1. 数组优点<a class="headerlink" href="#411" title="Permanent link">¶</a></h2>
|
<h2 id="411">4.1.1. 数组优点<a class="headerlink" href="#411" title="Permanent link">¶</a></h2>
|
||||||
<p><strong>在数组中访问元素非常高效</strong>。这是因为在数组中,计算元素的内存地址非常容易。给定数组首个元素的地址、和一个元素的索引,利用以下公式可以直接计算得到该元素的内存地址,从而直接访问此元素。</p>
|
<p><strong>在数组中访问元素非常高效</strong>。这是因为在数组中,计算元素的内存地址非常容易。给定数组首个元素的地址、和一个元素的索引,利用以下公式可以直接计算得到该元素的内存地址,从而直接访问此元素。</p>
|
||||||
<p><img alt="数组元素的内存地址计算" src="../array.assets/array_memory_location_calculation.png" /></p>
|
<p><img alt="数组元素的内存地址计算" src="../array.assets/array_memory_location_calculation.png" /></p>
|
||||||
|
<p align="center"> Fig. 数组元素的内存地址计算 </p>
|
||||||
|
|
||||||
<div class="highlight"><pre><span></span><code><a id="__codelineno-10-1" name="__codelineno-10-1" href="#__codelineno-10-1"></a><span class="c1"># 元素内存地址 = 数组内存地址 + 元素长度 * 元素索引</span>
|
<div class="highlight"><pre><span></span><code><a id="__codelineno-10-1" name="__codelineno-10-1" href="#__codelineno-10-1"></a><span class="c1"># 元素内存地址 = 数组内存地址 + 元素长度 * 元素索引</span>
|
||||||
<a id="__codelineno-10-2" name="__codelineno-10-2" href="#__codelineno-10-2"></a><span class="nv">elementAddr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span>firtstElementAddr<span class="w"> </span>+<span class="w"> </span>elementLength<span class="w"> </span>*<span class="w"> </span>elementIndex
|
<a id="__codelineno-10-2" name="__codelineno-10-2" href="#__codelineno-10-2"></a><span class="nv">elementAddr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span>firtstElementAddr<span class="w"> </span>+<span class="w"> </span>elementLength<span class="w"> </span>*<span class="w"> </span>elementIndex
|
||||||
</code></pre></div>
|
</code></pre></div>
|
||||||
|
@ -1939,6 +1943,8 @@
|
||||||
</div>
|
</div>
|
||||||
<p><strong>数组中插入或删除元素效率低下</strong>。如果我们想要在数组中间插入一个元素,由于数组元素在内存中是“紧挨着的”,它们之间没有空间再放任何数据。因此,我们不得不将此索引之后的所有元素都向后移动一位,然后再把元素赋值给该索引。</p>
|
<p><strong>数组中插入或删除元素效率低下</strong>。如果我们想要在数组中间插入一个元素,由于数组元素在内存中是“紧挨着的”,它们之间没有空间再放任何数据。因此,我们不得不将此索引之后的所有元素都向后移动一位,然后再把元素赋值给该索引。</p>
|
||||||
<p><img alt="数组插入元素" src="../array.assets/array_insert_element.png" /></p>
|
<p><img alt="数组插入元素" src="../array.assets/array_insert_element.png" /></p>
|
||||||
|
<p align="center"> Fig. 数组插入元素 </p>
|
||||||
|
|
||||||
<div class="tabbed-set tabbed-alternate" data-tabs="4:9"><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" /><input id="__tabbed_4_5" name="__tabbed_4" type="radio" /><input id="__tabbed_4_6" name="__tabbed_4" type="radio" /><input id="__tabbed_4_7" name="__tabbed_4" type="radio" /><input id="__tabbed_4_8" name="__tabbed_4" type="radio" /><input id="__tabbed_4_9" name="__tabbed_4" type="radio" /><div class="tabbed-labels"><label for="__tabbed_4_1">Java</label><label for="__tabbed_4_2">C++</label><label for="__tabbed_4_3">Python</label><label for="__tabbed_4_4">Go</label><label for="__tabbed_4_5">JavaScript</label><label for="__tabbed_4_6">TypeScript</label><label for="__tabbed_4_7">C</label><label for="__tabbed_4_8">C#</label><label for="__tabbed_4_9">Swift</label></div>
|
<div class="tabbed-set tabbed-alternate" data-tabs="4:9"><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" /><input id="__tabbed_4_5" name="__tabbed_4" type="radio" /><input id="__tabbed_4_6" name="__tabbed_4" type="radio" /><input id="__tabbed_4_7" name="__tabbed_4" type="radio" /><input id="__tabbed_4_8" name="__tabbed_4" type="radio" /><input id="__tabbed_4_9" name="__tabbed_4" type="radio" /><div class="tabbed-labels"><label for="__tabbed_4_1">Java</label><label for="__tabbed_4_2">C++</label><label for="__tabbed_4_3">Python</label><label for="__tabbed_4_4">Go</label><label for="__tabbed_4_5">JavaScript</label><label for="__tabbed_4_6">TypeScript</label><label for="__tabbed_4_7">C</label><label for="__tabbed_4_8">C#</label><label for="__tabbed_4_9">Swift</label></div>
|
||||||
<div class="tabbed-content">
|
<div class="tabbed-content">
|
||||||
<div class="tabbed-block">
|
<div class="tabbed-block">
|
||||||
|
@ -2045,6 +2051,8 @@
|
||||||
</div>
|
</div>
|
||||||
<p>删除元素也是类似,如果我们想要删除索引 <span class="arithmatex">\(i\)</span> 处的元素,则需要把索引 <span class="arithmatex">\(i\)</span> 之后的元素都向前移动一位。值得注意的是,删除元素后,原先末尾的元素变得“无意义”了,我们无需特意去修改它。</p>
|
<p>删除元素也是类似,如果我们想要删除索引 <span class="arithmatex">\(i\)</span> 处的元素,则需要把索引 <span class="arithmatex">\(i\)</span> 之后的元素都向前移动一位。值得注意的是,删除元素后,原先末尾的元素变得“无意义”了,我们无需特意去修改它。</p>
|
||||||
<p><img alt="数组删除元素" src="../array.assets/array_remove_element.png" /></p>
|
<p><img alt="数组删除元素" src="../array.assets/array_remove_element.png" /></p>
|
||||||
|
<p align="center"> Fig. 数组删除元素 </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-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-content">
|
||||||
<div class="tabbed-block">
|
<div class="tabbed-block">
|
||||||
|
|
|
@ -1607,6 +1607,8 @@
|
||||||
<p>「链表 Linked List」是一种线性数据结构,其中每个元素都是单独的对象,各个元素(一般称为结点)之间通过指针连接。由于结点中记录了连接关系,因此链表的存储方式相比于数组更加灵活,系统不必保证内存地址的连续性。</p>
|
<p>「链表 Linked List」是一种线性数据结构,其中每个元素都是单独的对象,各个元素(一般称为结点)之间通过指针连接。由于结点中记录了连接关系,因此链表的存储方式相比于数组更加灵活,系统不必保证内存地址的连续性。</p>
|
||||||
<p>链表的「结点 Node」包含两项数据,一是结点「值 Value」,二是指向下一结点的「指针 Pointer」(或称「引用 Reference」)。</p>
|
<p>链表的「结点 Node」包含两项数据,一是结点「值 Value」,二是指向下一结点的「指针 Pointer」(或称「引用 Reference」)。</p>
|
||||||
<p><img alt="链表定义与存储方式" src="../linked_list.assets/linkedlist_definition.png" /></p>
|
<p><img alt="链表定义与存储方式" src="../linked_list.assets/linkedlist_definition.png" /></p>
|
||||||
|
<p align="center"> Fig. 链表定义与存储方式 </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-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-content">
|
||||||
<div class="tabbed-block">
|
<div class="tabbed-block">
|
||||||
|
@ -1874,6 +1876,8 @@
|
||||||
<h2 id="421">4.2.1. 链表优点<a class="headerlink" href="#421" title="Permanent link">¶</a></h2>
|
<h2 id="421">4.2.1. 链表优点<a class="headerlink" href="#421" title="Permanent link">¶</a></h2>
|
||||||
<p><strong>在链表中,插入与删除结点的操作效率高</strong>。比如,如果我们想在链表中间的两个结点 <code>A</code> , <code>B</code> 之间插入一个新结点 <code>P</code> ,我们只需要改变两个结点指针即可,时间复杂度为 <span class="arithmatex">\(O(1)\)</span> ,相比数组的插入操作高效很多。</p>
|
<p><strong>在链表中,插入与删除结点的操作效率高</strong>。比如,如果我们想在链表中间的两个结点 <code>A</code> , <code>B</code> 之间插入一个新结点 <code>P</code> ,我们只需要改变两个结点指针即可,时间复杂度为 <span class="arithmatex">\(O(1)\)</span> ,相比数组的插入操作高效很多。</p>
|
||||||
<p><img alt="链表插入结点" src="../linked_list.assets/linkedlist_insert_node.png" /></p>
|
<p><img alt="链表插入结点" src="../linked_list.assets/linkedlist_insert_node.png" /></p>
|
||||||
|
<p align="center"> Fig. 链表插入结点 </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-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-content">
|
||||||
<div class="tabbed-block">
|
<div class="tabbed-block">
|
||||||
|
@ -1965,6 +1969,8 @@
|
||||||
</div>
|
</div>
|
||||||
<p>在链表中删除结点也很方便,只需要改变一个结点指针即可。如下图所示,虽然在完成删除后结点 <code>P</code> 仍然指向 <code>n2</code> ,但实际上 <code>P</code> 已经不属于此链表了,因为遍历此链表是无法访问到 <code>P</code> 的。</p>
|
<p>在链表中删除结点也很方便,只需要改变一个结点指针即可。如下图所示,虽然在完成删除后结点 <code>P</code> 仍然指向 <code>n2</code> ,但实际上 <code>P</code> 已经不属于此链表了,因为遍历此链表是无法访问到 <code>P</code> 的。</p>
|
||||||
<p><img alt="链表删除结点" src="../linked_list.assets/linkedlist_remove_node.png" /></p>
|
<p><img alt="链表删除结点" src="../linked_list.assets/linkedlist_remove_node.png" /></p>
|
||||||
|
<p align="center"> Fig. 链表删除结点 </p>
|
||||||
|
|
||||||
<div class="tabbed-set tabbed-alternate" data-tabs="4:10"><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" /><input id="__tabbed_4_5" name="__tabbed_4" type="radio" /><input id="__tabbed_4_6" name="__tabbed_4" type="radio" /><input id="__tabbed_4_7" name="__tabbed_4" type="radio" /><input id="__tabbed_4_8" name="__tabbed_4" type="radio" /><input id="__tabbed_4_9" name="__tabbed_4" type="radio" /><input id="__tabbed_4_10" name="__tabbed_4" type="radio" /><div class="tabbed-labels"><label for="__tabbed_4_1">Java</label><label for="__tabbed_4_2">C++</label><label for="__tabbed_4_3">Python</label><label for="__tabbed_4_4">Go</label><label for="__tabbed_4_5">JavaScript</label><label for="__tabbed_4_6">TypeScript</label><label for="__tabbed_4_7">C</label><label for="__tabbed_4_8">C#</label><label for="__tabbed_4_9">Swift</label><label for="__tabbed_4_10">Zig</label></div>
|
<div class="tabbed-set tabbed-alternate" data-tabs="4:10"><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" /><input id="__tabbed_4_5" name="__tabbed_4" type="radio" /><input id="__tabbed_4_6" name="__tabbed_4" type="radio" /><input id="__tabbed_4_7" name="__tabbed_4" type="radio" /><input id="__tabbed_4_8" name="__tabbed_4" type="radio" /><input id="__tabbed_4_9" name="__tabbed_4" type="radio" /><input id="__tabbed_4_10" name="__tabbed_4" type="radio" /><div class="tabbed-labels"><label for="__tabbed_4_1">Java</label><label for="__tabbed_4_2">C++</label><label for="__tabbed_4_3">Python</label><label for="__tabbed_4_4">Go</label><label for="__tabbed_4_5">JavaScript</label><label for="__tabbed_4_6">TypeScript</label><label for="__tabbed_4_7">C</label><label for="__tabbed_4_8">C#</label><label for="__tabbed_4_9">Swift</label><label for="__tabbed_4_10">Zig</label></div>
|
||||||
<div class="tabbed-content">
|
<div class="tabbed-content">
|
||||||
<div class="tabbed-block">
|
<div class="tabbed-block">
|
||||||
|
@ -2483,6 +2489,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p><img alt="常见链表种类" src="../linked_list.assets/linkedlist_common_types.png" /></p>
|
<p><img alt="常见链表种类" src="../linked_list.assets/linkedlist_common_types.png" /></p>
|
||||||
|
<p align="center"> Fig. 常见链表种类 </p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1687,6 +1687,8 @@
|
||||||
<li>「指令空间」用于保存编译后的程序指令,<strong>在实际统计中一般忽略不计</strong>。</li>
|
<li>「指令空间」用于保存编译后的程序指令,<strong>在实际统计中一般忽略不计</strong>。</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p><img alt="算法使用的相关空间" src="../space_complexity.assets/space_types.png" /></p>
|
<p><img alt="算法使用的相关空间" src="../space_complexity.assets/space_types.png" /></p>
|
||||||
|
<p align="center"> Fig. 算法使用的相关空间 </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-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-content">
|
||||||
<div class="tabbed-block">
|
<div class="tabbed-block">
|
||||||
|
@ -2171,6 +2173,8 @@ O(1) < O(\log n) < O(n) < O(n^2) < O(2^n) \newline
|
||||||
\end{aligned}
|
\end{aligned}
|
||||||
\]</div>
|
\]</div>
|
||||||
<p><img alt="空间复杂度的常见类型" src="../space_complexity.assets/space_complexity_common_types.png" /></p>
|
<p><img alt="空间复杂度的常见类型" src="../space_complexity.assets/space_complexity_common_types.png" /></p>
|
||||||
|
<p align="center"> Fig. 空间复杂度的常见类型 </p>
|
||||||
|
|
||||||
<div class="admonition tip">
|
<div class="admonition tip">
|
||||||
<p class="admonition-title">Tip</p>
|
<p class="admonition-title">Tip</p>
|
||||||
<p>部分示例代码需要一些前置知识,包括数组、链表、二叉树、递归算法等。如果遇到看不懂的地方无需担心,可以在学习完后面章节后再来复习,现阶段先聚焦在理解空间复杂度含义和推算方法上。</p>
|
<p>部分示例代码需要一些前置知识,包括数组、链表、二叉树、递归算法等。如果遇到看不懂的地方无需担心,可以在学习完后面章节后再来复习,现阶段先聚焦在理解空间复杂度含义和推算方法上。</p>
|
||||||
|
@ -2629,6 +2633,8 @@ O(1) < O(\log n) < O(n) < O(n^2) < O(2^n) \newline
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p><img alt="递归函数产生的线性阶空间复杂度" src="../space_complexity.assets/space_complexity_recursive_linear.png" /></p>
|
<p><img alt="递归函数产生的线性阶空间复杂度" src="../space_complexity.assets/space_complexity_recursive_linear.png" /></p>
|
||||||
|
<p align="center"> Fig. 递归函数产生的线性阶空间复杂度 </p>
|
||||||
|
|
||||||
<h3 id="on2">平方阶 <span class="arithmatex">\(O(n^2)\)</span><a class="headerlink" href="#on2" title="Permanent link">¶</a></h3>
|
<h3 id="on2">平方阶 <span class="arithmatex">\(O(n^2)\)</span><a class="headerlink" href="#on2" title="Permanent link">¶</a></h3>
|
||||||
<p>平方阶常见于元素数量与 <span class="arithmatex">\(n\)</span> 成平方关系的矩阵、图。</p>
|
<p>平方阶常见于元素数量与 <span class="arithmatex">\(n\)</span> 成平方关系的矩阵、图。</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-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>
|
||||||
|
@ -2877,6 +2883,8 @@ O(1) < O(\log n) < O(n) < O(n^2) < O(2^n) \newline
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p><img alt="递归函数产生的平方阶空间复杂度" src="../space_complexity.assets/space_complexity_recursive_quadratic.png" /></p>
|
<p><img alt="递归函数产生的平方阶空间复杂度" src="../space_complexity.assets/space_complexity_recursive_quadratic.png" /></p>
|
||||||
|
<p align="center"> Fig. 递归函数产生的平方阶空间复杂度 </p>
|
||||||
|
|
||||||
<h3 id="o2n">指数阶 <span class="arithmatex">\(O(2^n)\)</span><a class="headerlink" href="#o2n" title="Permanent link">¶</a></h3>
|
<h3 id="o2n">指数阶 <span class="arithmatex">\(O(2^n)\)</span><a class="headerlink" href="#o2n" title="Permanent link">¶</a></h3>
|
||||||
<p>指数阶常见于二叉树。高度为 <span class="arithmatex">\(n\)</span> 的「满二叉树」的结点数量为 <span class="arithmatex">\(2^n - 1\)</span> ,使用 <span class="arithmatex">\(O(2^n)\)</span> 空间。</p>
|
<p>指数阶常见于二叉树。高度为 <span class="arithmatex">\(n\)</span> 的「满二叉树」的结点数量为 <span class="arithmatex">\(2^n - 1\)</span> ,使用 <span class="arithmatex">\(O(2^n)\)</span> 空间。</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-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>
|
||||||
|
@ -2992,6 +3000,8 @@ O(1) < O(\log n) < O(n) < O(n^2) < O(2^n) \newline
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p><img alt="满二叉树产生的指数阶空间复杂度" src="../space_complexity.assets/space_complexity_exponential.png" /></p>
|
<p><img alt="满二叉树产生的指数阶空间复杂度" src="../space_complexity.assets/space_complexity_exponential.png" /></p>
|
||||||
|
<p align="center"> Fig. 满二叉树产生的指数阶空间复杂度 </p>
|
||||||
|
|
||||||
<h3 id="olog-n">对数阶 <span class="arithmatex">\(O(\log n)\)</span><a class="headerlink" href="#olog-n" title="Permanent link">¶</a></h3>
|
<h3 id="olog-n">对数阶 <span class="arithmatex">\(O(\log n)\)</span><a class="headerlink" href="#olog-n" title="Permanent link">¶</a></h3>
|
||||||
<p>对数阶常见于分治算法、数据类型转换等。</p>
|
<p>对数阶常见于分治算法、数据类型转换等。</p>
|
||||||
<p>例如「归并排序」,长度为 <span class="arithmatex">\(n\)</span> 的数组可以形成高度为 <span class="arithmatex">\(\log n\)</span> 的递归树,因此空间复杂度为 <span class="arithmatex">\(O(\log n)\)</span> 。</p>
|
<p>例如「归并排序」,长度为 <span class="arithmatex">\(n\)</span> 的数组可以形成高度为 <span class="arithmatex">\(\log n\)</span> 的递归树,因此空间复杂度为 <span class="arithmatex">\(O(\log n)\)</span> 。</p>
|
||||||
|
|
|
@ -2105,6 +2105,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p><img alt="算法 A, B, C 的时间增长趋势" src="../time_complexity.assets/time_complexity_simple_example.png" /></p>
|
<p><img alt="算法 A, B, C 的时间增长趋势" src="../time_complexity.assets/time_complexity_simple_example.png" /></p>
|
||||||
|
<p align="center"> Fig. 算法 A, B, C 的时间增长趋势 </p>
|
||||||
|
|
||||||
<p>相比直接统计算法运行时间,时间复杂度分析的做法有什么好处呢?以及有什么不足?</p>
|
<p>相比直接统计算法运行时间,时间复杂度分析的做法有什么好处呢?以及有什么不足?</p>
|
||||||
<p><strong>时间复杂度可以有效评估算法效率</strong>。算法 <code>B</code> 运行时间的增长是线性的,在 <span class="arithmatex">\(n > 1\)</span> 时慢于算法 <code>A</code> ,在 <span class="arithmatex">\(n > 1000000\)</span> 时慢于算法 <code>C</code> 。实质上,只要输入数据大小 <span class="arithmatex">\(n\)</span> 足够大,复杂度为「常数阶」的算法一定优于「线性阶」的算法,这也正是时间增长趋势的含义。</p>
|
<p><strong>时间复杂度可以有效评估算法效率</strong>。算法 <code>B</code> 运行时间的增长是线性的,在 <span class="arithmatex">\(n > 1\)</span> 时慢于算法 <code>A</code> ,在 <span class="arithmatex">\(n > 1000000\)</span> 时慢于算法 <code>C</code> 。实质上,只要输入数据大小 <span class="arithmatex">\(n\)</span> 足够大,复杂度为「常数阶」的算法一定优于「线性阶」的算法,这也正是时间增长趋势的含义。</p>
|
||||||
<p><strong>时间复杂度的推算方法更加简便</strong>。在时间复杂度分析中,我们可以将统计「计算操作的运行时间」简化为统计「计算操作的数量」,这是因为,无论是运行平台还是计算操作类型,都与算法运行时间的增长趋势无关。因而,我们可以简单地将所有计算操作的执行时间统一看作是相同的“单位时间”,这样的简化做法大大降低了估算难度。</p>
|
<p><strong>时间复杂度的推算方法更加简便</strong>。在时间复杂度分析中,我们可以将统计「计算操作的运行时间」简化为统计「计算操作的数量」,这是因为,无论是运行平台还是计算操作类型,都与算法运行时间的增长趋势无关。因而,我们可以简单地将所有计算操作的执行时间统一看作是相同的“单位时间”,这样的简化做法大大降低了估算难度。</p>
|
||||||
|
@ -2245,6 +2247,8 @@ T(n) = O(f(n))
|
||||||
$$</p>
|
$$</p>
|
||||||
</div>
|
</div>
|
||||||
<p><img alt="函数的渐近上界" src="../time_complexity.assets/asymptotic_upper_bound.png" /></p>
|
<p><img alt="函数的渐近上界" src="../time_complexity.assets/asymptotic_upper_bound.png" /></p>
|
||||||
|
<p align="center"> Fig. 函数的渐近上界 </p>
|
||||||
|
|
||||||
<p>本质上看,计算渐近上界就是在找一个函数 <span class="arithmatex">\(f(n)\)</span> ,<strong>使得在 <span class="arithmatex">\(n\)</span> 趋向于无穷大时,<span class="arithmatex">\(T(n)\)</span> 和 <span class="arithmatex">\(f(n)\)</span> 处于相同的增长级别(仅相差一个常数项 <span class="arithmatex">\(c\)</span> 的倍数)</strong>。</p>
|
<p>本质上看,计算渐近上界就是在找一个函数 <span class="arithmatex">\(f(n)\)</span> ,<strong>使得在 <span class="arithmatex">\(n\)</span> 趋向于无穷大时,<span class="arithmatex">\(T(n)\)</span> 和 <span class="arithmatex">\(f(n)\)</span> 处于相同的增长级别(仅相差一个常数项 <span class="arithmatex">\(c\)</span> 的倍数)</strong>。</p>
|
||||||
<div class="admonition tip">
|
<div class="admonition tip">
|
||||||
<p class="admonition-title">Tip</p>
|
<p class="admonition-title">Tip</p>
|
||||||
|
@ -2473,6 +2477,8 @@ O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!
|
||||||
\end{aligned}
|
\end{aligned}
|
||||||
\]</div>
|
\]</div>
|
||||||
<p><img alt="时间复杂度的常见类型" src="../time_complexity.assets/time_complexity_common_types.png" /></p>
|
<p><img alt="时间复杂度的常见类型" src="../time_complexity.assets/time_complexity_common_types.png" /></p>
|
||||||
|
<p align="center"> Fig. 时间复杂度的常见类型 </p>
|
||||||
|
|
||||||
<div class="admonition tip">
|
<div class="admonition tip">
|
||||||
<p class="admonition-title">Tip</p>
|
<p class="admonition-title">Tip</p>
|
||||||
<p>部分示例代码需要一些前置知识,包括数组、递归算法等。如果遇到看不懂的地方无需担心,可以在学习完后面章节后再来复习,现阶段先聚焦在理解时间复杂度含义和推算方法上。</p>
|
<p>部分示例代码需要一些前置知识,包括数组、递归算法等。如果遇到看不懂的地方无需担心,可以在学习完后面章节后再来复习,现阶段先聚焦在理解时间复杂度含义和推算方法上。</p>
|
||||||
|
@ -2952,6 +2958,8 @@ O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p><img alt="常数阶、线性阶、平方阶的时间复杂度" src="../time_complexity.assets/time_complexity_constant_linear_quadratic.png" /></p>
|
<p><img alt="常数阶、线性阶、平方阶的时间复杂度" src="../time_complexity.assets/time_complexity_constant_linear_quadratic.png" /></p>
|
||||||
|
<p align="center"> Fig. 常数阶、线性阶、平方阶的时间复杂度 </p>
|
||||||
|
|
||||||
<p>以「冒泡排序」为例,外层循环 <span class="arithmatex">\(n - 1\)</span> 次,内层循环 <span class="arithmatex">\(n-1, n-2, \cdots, 2, 1\)</span> 次,平均为 <span class="arithmatex">\(\frac{n}{2}\)</span> 次,因此时间复杂度为 <span class="arithmatex">\(O(n^2)\)</span> 。</p>
|
<p>以「冒泡排序」为例,外层循环 <span class="arithmatex">\(n - 1\)</span> 次,内层循环 <span class="arithmatex">\(n-1, n-2, \cdots, 2, 1\)</span> 次,平均为 <span class="arithmatex">\(\frac{n}{2}\)</span> 次,因此时间复杂度为 <span class="arithmatex">\(O(n^2)\)</span> 。</p>
|
||||||
<div class="arithmatex">\[
|
<div class="arithmatex">\[
|
||||||
O((n - 1) \frac{n}{2}) = O(n^2)
|
O((n - 1) \frac{n}{2}) = O(n^2)
|
||||||
|
@ -3320,6 +3328,8 @@ O((n - 1) \frac{n}{2}) = O(n^2)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p><img alt="指数阶的时间复杂度" src="../time_complexity.assets/time_complexity_exponential.png" /></p>
|
<p><img alt="指数阶的时间复杂度" src="../time_complexity.assets/time_complexity_exponential.png" /></p>
|
||||||
|
<p align="center"> Fig. 指数阶的时间复杂度 </p>
|
||||||
|
|
||||||
<p>在实际算法中,指数阶常出现于递归函数。例如以下代码,不断地一分为二,分裂 <span class="arithmatex">\(n\)</span> 次后停止。</p>
|
<p>在实际算法中,指数阶常出现于递归函数。例如以下代码,不断地一分为二,分裂 <span class="arithmatex">\(n\)</span> 次后停止。</p>
|
||||||
<div class="tabbed-set tabbed-alternate" data-tabs="11:10"><input checked="checked" id="__tabbed_11_1" name="__tabbed_11" type="radio" /><input id="__tabbed_11_2" name="__tabbed_11" type="radio" /><input id="__tabbed_11_3" name="__tabbed_11" type="radio" /><input id="__tabbed_11_4" name="__tabbed_11" type="radio" /><input id="__tabbed_11_5" name="__tabbed_11" type="radio" /><input id="__tabbed_11_6" name="__tabbed_11" type="radio" /><input id="__tabbed_11_7" name="__tabbed_11" type="radio" /><input id="__tabbed_11_8" name="__tabbed_11" type="radio" /><input id="__tabbed_11_9" name="__tabbed_11" type="radio" /><input id="__tabbed_11_10" name="__tabbed_11" type="radio" /><div class="tabbed-labels"><label for="__tabbed_11_1">Java</label><label for="__tabbed_11_2">C++</label><label for="__tabbed_11_3">Python</label><label for="__tabbed_11_4">Go</label><label for="__tabbed_11_5">JavaScript</label><label for="__tabbed_11_6">TypeScript</label><label for="__tabbed_11_7">C</label><label for="__tabbed_11_8">C#</label><label for="__tabbed_11_9">Swift</label><label for="__tabbed_11_10">Zig</label></div>
|
<div class="tabbed-set tabbed-alternate" data-tabs="11:10"><input checked="checked" id="__tabbed_11_1" name="__tabbed_11" type="radio" /><input id="__tabbed_11_2" name="__tabbed_11" type="radio" /><input id="__tabbed_11_3" name="__tabbed_11" type="radio" /><input id="__tabbed_11_4" name="__tabbed_11" type="radio" /><input id="__tabbed_11_5" name="__tabbed_11" type="radio" /><input id="__tabbed_11_6" name="__tabbed_11" type="radio" /><input id="__tabbed_11_7" name="__tabbed_11" type="radio" /><input id="__tabbed_11_8" name="__tabbed_11" type="radio" /><input id="__tabbed_11_9" name="__tabbed_11" type="radio" /><input id="__tabbed_11_10" name="__tabbed_11" type="radio" /><div class="tabbed-labels"><label for="__tabbed_11_1">Java</label><label for="__tabbed_11_2">C++</label><label for="__tabbed_11_3">Python</label><label for="__tabbed_11_4">Go</label><label for="__tabbed_11_5">JavaScript</label><label for="__tabbed_11_6">TypeScript</label><label for="__tabbed_11_7">C</label><label for="__tabbed_11_8">C#</label><label for="__tabbed_11_9">Swift</label><label for="__tabbed_11_10">Zig</label></div>
|
||||||
<div class="tabbed-content">
|
<div class="tabbed-content">
|
||||||
|
@ -3529,6 +3539,8 @@ O((n - 1) \frac{n}{2}) = O(n^2)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p><img alt="对数阶的时间复杂度" src="../time_complexity.assets/time_complexity_logarithmic.png" /></p>
|
<p><img alt="对数阶的时间复杂度" src="../time_complexity.assets/time_complexity_logarithmic.png" /></p>
|
||||||
|
<p align="center"> Fig. 对数阶的时间复杂度 </p>
|
||||||
|
|
||||||
<p>与指数阶类似,对数阶也常出现于递归函数。以下代码形成了一个高度为 <span class="arithmatex">\(\log_2 n\)</span> 的递归树。</p>
|
<p>与指数阶类似,对数阶也常出现于递归函数。以下代码形成了一个高度为 <span class="arithmatex">\(\log_2 n\)</span> 的递归树。</p>
|
||||||
<div class="tabbed-set tabbed-alternate" data-tabs="13:10"><input checked="checked" id="__tabbed_13_1" name="__tabbed_13" type="radio" /><input id="__tabbed_13_2" name="__tabbed_13" type="radio" /><input id="__tabbed_13_3" name="__tabbed_13" type="radio" /><input id="__tabbed_13_4" name="__tabbed_13" type="radio" /><input id="__tabbed_13_5" name="__tabbed_13" type="radio" /><input id="__tabbed_13_6" name="__tabbed_13" type="radio" /><input id="__tabbed_13_7" name="__tabbed_13" type="radio" /><input id="__tabbed_13_8" name="__tabbed_13" type="radio" /><input id="__tabbed_13_9" name="__tabbed_13" type="radio" /><input id="__tabbed_13_10" name="__tabbed_13" type="radio" /><div class="tabbed-labels"><label for="__tabbed_13_1">Java</label><label for="__tabbed_13_2">C++</label><label for="__tabbed_13_3">Python</label><label for="__tabbed_13_4">Go</label><label for="__tabbed_13_5">JavaScript</label><label for="__tabbed_13_6">TypeScript</label><label for="__tabbed_13_7">C</label><label for="__tabbed_13_8">C#</label><label for="__tabbed_13_9">Swift</label><label for="__tabbed_13_10">Zig</label></div>
|
<div class="tabbed-set tabbed-alternate" data-tabs="13:10"><input checked="checked" id="__tabbed_13_1" name="__tabbed_13" type="radio" /><input id="__tabbed_13_2" name="__tabbed_13" type="radio" /><input id="__tabbed_13_3" name="__tabbed_13" type="radio" /><input id="__tabbed_13_4" name="__tabbed_13" type="radio" /><input id="__tabbed_13_5" name="__tabbed_13" type="radio" /><input id="__tabbed_13_6" name="__tabbed_13" type="radio" /><input id="__tabbed_13_7" name="__tabbed_13" type="radio" /><input id="__tabbed_13_8" name="__tabbed_13" type="radio" /><input id="__tabbed_13_9" name="__tabbed_13" type="radio" /><input id="__tabbed_13_10" name="__tabbed_13" type="radio" /><div class="tabbed-labels"><label for="__tabbed_13_1">Java</label><label for="__tabbed_13_2">C++</label><label for="__tabbed_13_3">Python</label><label for="__tabbed_13_4">Go</label><label for="__tabbed_13_5">JavaScript</label><label for="__tabbed_13_6">TypeScript</label><label for="__tabbed_13_7">C</label><label for="__tabbed_13_8">C#</label><label for="__tabbed_13_9">Swift</label><label for="__tabbed_13_10">Zig</label></div>
|
||||||
<div class="tabbed-content">
|
<div class="tabbed-content">
|
||||||
|
@ -3745,6 +3757,8 @@ O((n - 1) \frac{n}{2}) = O(n^2)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p><img alt="线性对数阶的时间复杂度" src="../time_complexity.assets/time_complexity_logarithmic_linear.png" /></p>
|
<p><img alt="线性对数阶的时间复杂度" src="../time_complexity.assets/time_complexity_logarithmic_linear.png" /></p>
|
||||||
|
<p align="center"> Fig. 线性对数阶的时间复杂度 </p>
|
||||||
|
|
||||||
<h3 id="on_1">阶乘阶 <span class="arithmatex">\(O(n!)\)</span><a class="headerlink" href="#on_1" title="Permanent link">¶</a></h3>
|
<h3 id="on_1">阶乘阶 <span class="arithmatex">\(O(n!)\)</span><a class="headerlink" href="#on_1" title="Permanent link">¶</a></h3>
|
||||||
<p>阶乘阶对应数学上的「全排列」。即给定 <span class="arithmatex">\(n\)</span> 个互不重复的元素,求其所有可能的排列方案,则方案数量为</p>
|
<p>阶乘阶对应数学上的「全排列」。即给定 <span class="arithmatex">\(n\)</span> 个互不重复的元素,求其所有可能的排列方案,则方案数量为</p>
|
||||||
<div class="arithmatex">\[
|
<div class="arithmatex">\[
|
||||||
|
@ -3882,6 +3896,8 @@ n! = n \times (n - 1) \times (n - 2) \times \cdots \times 2 \times 1
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p><img alt="阶乘阶的时间复杂度" src="../time_complexity.assets/time_complexity_factorial.png" /></p>
|
<p><img alt="阶乘阶的时间复杂度" src="../time_complexity.assets/time_complexity_factorial.png" /></p>
|
||||||
|
<p align="center"> Fig. 阶乘阶的时间复杂度 </p>
|
||||||
|
|
||||||
<h2 id="226">2.2.6. 最差、最佳、平均时间复杂度<a class="headerlink" href="#226" title="Permanent link">¶</a></h2>
|
<h2 id="226">2.2.6. 最差、最佳、平均时间复杂度<a class="headerlink" href="#226" title="Permanent link">¶</a></h2>
|
||||||
<p><strong>某些算法的时间复杂度不是恒定的,而是与输入数据的分布有关</strong>。举一个例子,输入一个长度为 <span class="arithmatex">\(n\)</span> 数组 <code>nums</code> ,其中 <code>nums</code> 由从 <span class="arithmatex">\(1\)</span> 至 <span class="arithmatex">\(n\)</span> 的数字组成,但元素顺序是随机打乱的;算法的任务是返回元素 <span class="arithmatex">\(1\)</span> 的索引。我们可以得出以下结论:</p>
|
<p><strong>某些算法的时间复杂度不是恒定的,而是与输入数据的分布有关</strong>。举一个例子,输入一个长度为 <span class="arithmatex">\(n\)</span> 数组 <code>nums</code> ,其中 <code>nums</code> 由从 <span class="arithmatex">\(1\)</span> 至 <span class="arithmatex">\(n\)</span> 的数字组成,但元素顺序是随机打乱的;算法的任务是返回元素 <span class="arithmatex">\(1\)</span> 的索引。我们可以得出以下结论:</p>
|
||||||
<ul>
|
<ul>
|
||||||
|
|
|
@ -1581,6 +1581,8 @@
|
||||||
<li><strong>非线性数据结构</strong>:树、图、堆、哈希表;</li>
|
<li><strong>非线性数据结构</strong>:树、图、堆、哈希表;</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p><img alt="线性与非线性数据结构" src="../classification_of_data_structure.assets/classification_logic_structure.png" /></p>
|
<p><img alt="线性与非线性数据结构" src="../classification_of_data_structure.assets/classification_logic_structure.png" /></p>
|
||||||
|
<p align="center"> Fig. 线性与非线性数据结构 </p>
|
||||||
|
|
||||||
<h2 id="322">3.2.2. 物理结构:连续与离散<a class="headerlink" href="#322" title="Permanent link">¶</a></h2>
|
<h2 id="322">3.2.2. 物理结构:连续与离散<a class="headerlink" href="#322" title="Permanent link">¶</a></h2>
|
||||||
<div class="admonition note">
|
<div class="admonition note">
|
||||||
<p class="admonition-title">Note</p>
|
<p class="admonition-title">Note</p>
|
||||||
|
@ -1588,6 +1590,8 @@
|
||||||
</div>
|
</div>
|
||||||
<p><strong>「物理结构」反映了数据在计算机内存中的存储方式</strong>。从本质上看,分别是 <strong>数组的连续空间存储</strong> 和 <strong>链表的离散空间存储</strong>。物理结构从底层上决定了数据的访问、更新、增删等操作方法,在时间效率和空间效率方面呈现出此消彼长的特性。</p>
|
<p><strong>「物理结构」反映了数据在计算机内存中的存储方式</strong>。从本质上看,分别是 <strong>数组的连续空间存储</strong> 和 <strong>链表的离散空间存储</strong>。物理结构从底层上决定了数据的访问、更新、增删等操作方法,在时间效率和空间效率方面呈现出此消彼长的特性。</p>
|
||||||
<p><img alt="连续空间存储与离散空间存储" src="../classification_of_data_structure.assets/classification_phisical_structure.png" /></p>
|
<p><img alt="连续空间存储与离散空间存储" src="../classification_of_data_structure.assets/classification_phisical_structure.png" /></p>
|
||||||
|
<p align="center"> Fig. 连续空间存储与离散空间存储 </p>
|
||||||
|
|
||||||
<p><strong>所有数据结构都是基于数组、或链表、或两者组合实现的</strong>。例如栈和队列,既可以使用数组实现、也可以使用链表实现,而例如哈希表,其实现同时包含了数组和链表。</p>
|
<p><strong>所有数据结构都是基于数组、或链表、或两者组合实现的</strong>。例如栈和队列,既可以使用数组实现、也可以使用链表实现,而例如哈希表,其实现同时包含了数组和链表。</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>基于数组可实现</strong>:栈、队列、哈希表、树、堆、图、矩阵、张量(维度 <span class="arithmatex">\(\geq 3\)</span> 的数组)等;</li>
|
<li><strong>基于数组可实现</strong>:栈、队列、哈希表、树、堆、图、矩阵、张量(维度 <span class="arithmatex">\(\geq 3\)</span> 的数组)等;</li>
|
||||||
|
|
|
@ -1747,6 +1747,8 @@
|
||||||
\end{aligned}
|
\end{aligned}
|
||||||
\]</div>
|
\]</div>
|
||||||
<p><img alt="IEEE 754 标准下的 float 表示方式" src="../data_and_memory.assets/ieee_754_float.png" /></p>
|
<p><img alt="IEEE 754 标准下的 float 表示方式" src="../data_and_memory.assets/ieee_754_float.png" /></p>
|
||||||
|
<p align="center"> Fig. IEEE 754 标准下的 float 表示方式 </p>
|
||||||
|
|
||||||
<p>以上图为例,<span class="arithmatex">\(\mathrm{S} = 0\)</span> , <span class="arithmatex">\(\mathrm{E} = 124\)</span> ,<span class="arithmatex">\(\mathrm{N} = 2^{-2} + 2^{-3} = 0.375\)</span> ,易得</p>
|
<p>以上图为例,<span class="arithmatex">\(\mathrm{S} = 0\)</span> , <span class="arithmatex">\(\mathrm{E} = 124\)</span> ,<span class="arithmatex">\(\mathrm{N} = 2^{-2} + 2^{-3} = 0.375\)</span> ,易得</p>
|
||||||
<div class="arithmatex">\[
|
<div class="arithmatex">\[
|
||||||
\text { val } = (-1)^0 \times 2^{124 - 127} \times (1 + 0.375) = 0.171875
|
\text { val } = (-1)^0 \times 2^{124 - 127} \times (1 + 0.375) = 0.171875
|
||||||
|
@ -1871,6 +1873,8 @@
|
||||||
<p><strong>算法运行中,相关数据都被存储在内存中</strong>。下图展示了一个计算机内存条,其中每个黑色方块都包含一块内存空间。我们可以将内存想象成一个巨大的 Excel 表格,其中每个单元格都可以存储 1 byte 的数据,在算法运行时,所有数据都被存储在这些单元格中。</p>
|
<p><strong>算法运行中,相关数据都被存储在内存中</strong>。下图展示了一个计算机内存条,其中每个黑色方块都包含一块内存空间。我们可以将内存想象成一个巨大的 Excel 表格,其中每个单元格都可以存储 1 byte 的数据,在算法运行时,所有数据都被存储在这些单元格中。</p>
|
||||||
<p><strong>系统通过「内存地址 Memory Location」来访问目标内存位置的数据</strong>。计算机根据特定规则给表格中每个单元格编号,保证每块内存空间都有独立的内存地址。自此,程序便通过这些地址,访问内存中的数据。</p>
|
<p><strong>系统通过「内存地址 Memory Location」来访问目标内存位置的数据</strong>。计算机根据特定规则给表格中每个单元格编号,保证每块内存空间都有独立的内存地址。自此,程序便通过这些地址,访问内存中的数据。</p>
|
||||||
<p><img alt="内存条、内存空间、内存地址" src="../data_and_memory.assets/computer_memory_location.png" /></p>
|
<p><img alt="内存条、内存空间、内存地址" src="../data_and_memory.assets/computer_memory_location.png" /></p>
|
||||||
|
<p align="center"> Fig. 内存条、内存空间、内存地址 </p>
|
||||||
|
|
||||||
<p><strong>内存资源是设计数据结构与算法的重要考虑因素</strong>。内存是所有程序的公共资源,当内存被某程序占用时,不能被其它程序同时使用。我们需要根据剩余内存资源的情况来设计算法。例如,若剩余内存空间有限,则要求算法占用的峰值内存不能超过系统剩余内存;若运行的程序很多、缺少大块连续的内存空间,则要求选取的数据结构必须能够存储在离散的内存空间内。</p>
|
<p><strong>内存资源是设计数据结构与算法的重要考虑因素</strong>。内存是所有程序的公共资源,当内存被某程序占用时,不能被其它程序同时使用。我们需要根据剩余内存资源的情况来设计算法。例如,若剩余内存空间有限,则要求算法占用的峰值内存不能超过系统剩余内存;若运行的程序很多、缺少大块连续的内存空间,则要求选取的数据结构必须能够存储在离散的内存空间内。</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1649,6 +1649,8 @@ G & = \{ V, E \} \newline
|
||||||
\end{aligned}
|
\end{aligned}
|
||||||
\]</div>
|
\]</div>
|
||||||
<p><img alt="链表、树、图之间的关系" src="../graph.assets/linkedlist_tree_graph.png" /></p>
|
<p><img alt="链表、树、图之间的关系" src="../graph.assets/linkedlist_tree_graph.png" /></p>
|
||||||
|
<p align="center"> Fig. 链表、树、图之间的关系 </p>
|
||||||
|
|
||||||
<p>那么,图与其他数据结构的关系是什么?如果我们把「顶点」看作结点,把「边」看作连接各个结点的指针,则可将「图」看成一种从「链表」拓展而来的数据结构。<strong>相比线性关系(链表)和分治关系(树),网络关系(图)的自由度更高,也从而更为复杂</strong>。</p>
|
<p>那么,图与其他数据结构的关系是什么?如果我们把「顶点」看作结点,把「边」看作连接各个结点的指针,则可将「图」看成一种从「链表」拓展而来的数据结构。<strong>相比线性关系(链表)和分治关系(树),网络关系(图)的自由度更高,也从而更为复杂</strong>。</p>
|
||||||
<h2 id="911">9.1.1. 图常见类型<a class="headerlink" href="#911" title="Permanent link">¶</a></h2>
|
<h2 id="911">9.1.1. 图常见类型<a class="headerlink" href="#911" title="Permanent link">¶</a></h2>
|
||||||
<p>根据边是否有方向,分为「无向图 Undirected Graph」和「有向图 Directed Graph」。</p>
|
<p>根据边是否有方向,分为「无向图 Undirected Graph」和「有向图 Directed Graph」。</p>
|
||||||
|
@ -1657,14 +1659,20 @@ G & = \{ V, E \} \newline
|
||||||
<li>在有向图中,边是有方向的,即 <span class="arithmatex">\(A \rightarrow B\)</span> 和 <span class="arithmatex">\(A \leftarrow B\)</span> 两个方向的边是相互独立的,例如微博或抖音上的“关注”与“被关注”关系;</li>
|
<li>在有向图中,边是有方向的,即 <span class="arithmatex">\(A \rightarrow B\)</span> 和 <span class="arithmatex">\(A \leftarrow B\)</span> 两个方向的边是相互独立的,例如微博或抖音上的“关注”与“被关注”关系;</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p><img alt="有向图与无向图" src="../graph.assets/directed_graph.png" /></p>
|
<p><img alt="有向图与无向图" src="../graph.assets/directed_graph.png" /></p>
|
||||||
|
<p align="center"> Fig. 有向图与无向图 </p>
|
||||||
|
|
||||||
<p>根据所有顶点是否连通,分为「连通图 Connected Graph」和「非连通图 Disconnected Graph」。</p>
|
<p>根据所有顶点是否连通,分为「连通图 Connected Graph」和「非连通图 Disconnected Graph」。</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>对于连通图,从某个顶点出发,可以到达其余任意顶点;</li>
|
<li>对于连通图,从某个顶点出发,可以到达其余任意顶点;</li>
|
||||||
<li>对于非连通图,从某个顶点出发,至少有一个顶点无法到达;</li>
|
<li>对于非连通图,从某个顶点出发,至少有一个顶点无法到达;</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p><img alt="连通图与非连通图" src="../graph.assets/connected_graph.png" /></p>
|
<p><img alt="连通图与非连通图" src="../graph.assets/connected_graph.png" /></p>
|
||||||
|
<p align="center"> Fig. 连通图与非连通图 </p>
|
||||||
|
|
||||||
<p>我们可以给边添加“权重”变量,得到「有权图 Weighted Graph」。例如,在王者荣耀等游戏中,系统会根据共同游戏时间来计算玩家之间的“亲密度”,这种亲密度网络就可以使用有权图来表示。</p>
|
<p>我们可以给边添加“权重”变量,得到「有权图 Weighted Graph」。例如,在王者荣耀等游戏中,系统会根据共同游戏时间来计算玩家之间的“亲密度”,这种亲密度网络就可以使用有权图来表示。</p>
|
||||||
<p><img alt="有权图与无权图" src="../graph.assets/weighted_graph.png" /></p>
|
<p><img alt="有权图与无权图" src="../graph.assets/weighted_graph.png" /></p>
|
||||||
|
<p align="center"> Fig. 有权图与无权图 </p>
|
||||||
|
|
||||||
<h2 id="912">9.1.2. 图常用术语<a class="headerlink" href="#912" title="Permanent link">¶</a></h2>
|
<h2 id="912">9.1.2. 图常用术语<a class="headerlink" href="#912" title="Permanent link">¶</a></h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>「邻接 Adjacency」:当两顶点之间有边相连时,称此两顶点“邻接”。</li>
|
<li>「邻接 Adjacency」:当两顶点之间有边相连时,称此两顶点“邻接”。</li>
|
||||||
|
@ -1677,6 +1685,8 @@ G & = \{ V, E \} \newline
|
||||||
<p>设图的顶点数量为 <span class="arithmatex">\(n\)</span> ,「邻接矩阵 Adjacency Matrix」使用一个 <span class="arithmatex">\(n \times n\)</span> 大小的矩阵来表示图,每一行(列)代表一个顶点,矩阵元素代表边,使用 <span class="arithmatex">\(1\)</span> 或 <span class="arithmatex">\(0\)</span> 来表示两个顶点之间有边或无边。</p>
|
<p>设图的顶点数量为 <span class="arithmatex">\(n\)</span> ,「邻接矩阵 Adjacency Matrix」使用一个 <span class="arithmatex">\(n \times n\)</span> 大小的矩阵来表示图,每一行(列)代表一个顶点,矩阵元素代表边,使用 <span class="arithmatex">\(1\)</span> 或 <span class="arithmatex">\(0\)</span> 来表示两个顶点之间有边或无边。</p>
|
||||||
<p>如下图所示,记邻接矩阵为 <span class="arithmatex">\(M\)</span> 、顶点列表为 <span class="arithmatex">\(V\)</span> ,则矩阵元素 <span class="arithmatex">\(M[i][j] = 1\)</span> 代表着顶点 <span class="arithmatex">\(V[i]\)</span> 到顶点 <span class="arithmatex">\(V[j]\)</span> 之间有边,相反地 <span class="arithmatex">\(M[i][j] = 0\)</span> 代表两顶点之间无边。</p>
|
<p>如下图所示,记邻接矩阵为 <span class="arithmatex">\(M\)</span> 、顶点列表为 <span class="arithmatex">\(V\)</span> ,则矩阵元素 <span class="arithmatex">\(M[i][j] = 1\)</span> 代表着顶点 <span class="arithmatex">\(V[i]\)</span> 到顶点 <span class="arithmatex">\(V[j]\)</span> 之间有边,相反地 <span class="arithmatex">\(M[i][j] = 0\)</span> 代表两顶点之间无边。</p>
|
||||||
<p><img alt="图的邻接矩阵表示" src="../graph.assets/adjacency_matrix.png" /></p>
|
<p><img alt="图的邻接矩阵表示" src="../graph.assets/adjacency_matrix.png" /></p>
|
||||||
|
<p align="center"> Fig. 图的邻接矩阵表示 </p>
|
||||||
|
|
||||||
<p>邻接矩阵具有以下性质:</p>
|
<p>邻接矩阵具有以下性质:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>顶点不能与自身相连,因而邻接矩阵主对角线元素没有意义。</li>
|
<li>顶点不能与自身相连,因而邻接矩阵主对角线元素没有意义。</li>
|
||||||
|
@ -1687,6 +1697,8 @@ G & = \{ V, E \} \newline
|
||||||
<h3 id="_2">邻接表<a class="headerlink" href="#_2" title="Permanent link">¶</a></h3>
|
<h3 id="_2">邻接表<a class="headerlink" href="#_2" title="Permanent link">¶</a></h3>
|
||||||
<p>「邻接表 Adjacency List」使用 <span class="arithmatex">\(n\)</span> 个链表来表示图,链表结点表示顶点。第 <span class="arithmatex">\(i\)</span> 条链表对应顶点 <span class="arithmatex">\(i\)</span> ,其中存储了所有与该顶点相连的顶点。</p>
|
<p>「邻接表 Adjacency List」使用 <span class="arithmatex">\(n\)</span> 个链表来表示图,链表结点表示顶点。第 <span class="arithmatex">\(i\)</span> 条链表对应顶点 <span class="arithmatex">\(i\)</span> ,其中存储了所有与该顶点相连的顶点。</p>
|
||||||
<p><img alt="图的邻接表表示" src="../graph.assets/adjacency_list.png" /></p>
|
<p><img alt="图的邻接表表示" src="../graph.assets/adjacency_list.png" /></p>
|
||||||
|
<p align="center"> Fig. 图的邻接表表示 </p>
|
||||||
|
|
||||||
<p>邻接表仅存储存在的边,而边的总数往往远小于 <span class="arithmatex">\(n^2\)</span> ,因此更加节省空间。但是,因为在邻接表中需要通过遍历链表来查找边,所以其时间效率不如邻接矩阵。</p>
|
<p>邻接表仅存储存在的边,而边的总数往往远小于 <span class="arithmatex">\(n^2\)</span> ,因此更加节省空间。但是,因为在邻接表中需要通过遍历链表来查找边,所以其时间效率不如邻接矩阵。</p>
|
||||||
<p>观察上图发现,<strong>邻接表结构与哈希表「链地址法」非常相似,因此我们也可以用类似方法来优化效率</strong>。比如,当链表较长时,可以把链表转化为「AVL 树」,从而将时间效率从 <span class="arithmatex">\(O(n)\)</span> 优化至 <span class="arithmatex">\(O(\log n)\)</span> ,还可以通过中序遍历获取有序序列;还可以将链表转化为 HashSet(即哈希表),将时间复杂度降低至 <span class="arithmatex">\(O(1)\)</span> 。</p>
|
<p>观察上图发现,<strong>邻接表结构与哈希表「链地址法」非常相似,因此我们也可以用类似方法来优化效率</strong>。比如,当链表较长时,可以把链表转化为「AVL 树」,从而将时间效率从 <span class="arithmatex">\(O(n)\)</span> 优化至 <span class="arithmatex">\(O(\log n)\)</span> ,还可以通过中序遍历获取有序序列;还可以将链表转化为 HashSet(即哈希表),将时间复杂度降低至 <span class="arithmatex">\(O(1)\)</span> 。</p>
|
||||||
<h2 id="914">9.1.4. 图常见应用<a class="headerlink" href="#914" title="Permanent link">¶</a></h2>
|
<h2 id="914">9.1.4. 图常见应用<a class="headerlink" href="#914" title="Permanent link">¶</a></h2>
|
||||||
|
|
|
@ -1661,6 +1661,8 @@
|
||||||
<h2 id="931">9.3.1. 广度优先遍历<a class="headerlink" href="#931" title="Permanent link">¶</a></h2>
|
<h2 id="931">9.3.1. 广度优先遍历<a class="headerlink" href="#931" title="Permanent link">¶</a></h2>
|
||||||
<p><strong>广度优先遍历优是一种由近及远的遍历方式,从距离最近的顶点开始访问,并一层层向外扩张</strong>。具体地,从某个顶点出发,先遍历该顶点的所有邻接顶点,随后遍历下个顶点的所有邻接顶点,以此类推……</p>
|
<p><strong>广度优先遍历优是一种由近及远的遍历方式,从距离最近的顶点开始访问,并一层层向外扩张</strong>。具体地,从某个顶点出发,先遍历该顶点的所有邻接顶点,随后遍历下个顶点的所有邻接顶点,以此类推……</p>
|
||||||
<p><img alt="图的广度优先遍历" src="../graph_traversal.assets/graph_bfs.png" /></p>
|
<p><img alt="图的广度优先遍历" src="../graph_traversal.assets/graph_bfs.png" /></p>
|
||||||
|
<p align="center"> Fig. 图的广度优先遍历 </p>
|
||||||
|
|
||||||
<h3 id="_1">算法实现<a class="headerlink" href="#_1" title="Permanent link">¶</a></h3>
|
<h3 id="_1">算法实现<a class="headerlink" href="#_1" title="Permanent link">¶</a></h3>
|
||||||
<p>BFS 常借助「队列」来实现。队列具有“先入先出”的性质,这与 BFS “由近及远”的思想是异曲同工的。</p>
|
<p>BFS 常借助「队列」来实现。队列具有“先入先出”的性质,这与 BFS “由近及远”的思想是异曲同工的。</p>
|
||||||
<ol>
|
<ol>
|
||||||
|
@ -1878,6 +1880,8 @@
|
||||||
<h2 id="932">9.3.2. 深度优先遍历<a class="headerlink" href="#932" title="Permanent link">¶</a></h2>
|
<h2 id="932">9.3.2. 深度优先遍历<a class="headerlink" href="#932" title="Permanent link">¶</a></h2>
|
||||||
<p><strong>深度优先遍历是一种优先走到底、无路可走再回头的遍历方式</strong>。具体地,从某个顶点出发,不断地访问当前结点的某个邻接顶点,直到走到尽头时回溯,再继续走到底 + 回溯,以此类推……直至所有顶点遍历完成时结束。</p>
|
<p><strong>深度优先遍历是一种优先走到底、无路可走再回头的遍历方式</strong>。具体地,从某个顶点出发,不断地访问当前结点的某个邻接顶点,直到走到尽头时回溯,再继续走到底 + 回溯,以此类推……直至所有顶点遍历完成时结束。</p>
|
||||||
<p><img alt="图的深度优先遍历" src="../graph_traversal.assets/graph_dfs.png" /></p>
|
<p><img alt="图的深度优先遍历" src="../graph_traversal.assets/graph_dfs.png" /></p>
|
||||||
|
<p align="center"> Fig. 图的深度优先遍历 </p>
|
||||||
|
|
||||||
<h3 id="_3">算法实现<a class="headerlink" href="#_3" title="Permanent link">¶</a></h3>
|
<h3 id="_3">算法实现<a class="headerlink" href="#_3" title="Permanent link">¶</a></h3>
|
||||||
<p>这种“走到头 + 回溯”的算法形式一般基于递归来实现。与 BFS 类似,在 DFS 中我们也需要借助一个哈希表 <code>visited</code> 来记录已被访问的顶点,以避免重复访问顶点。</p>
|
<p>这种“走到头 + 回溯”的算法形式一般基于递归来实现。与 BFS 类似,在 DFS 中我们也需要借助一个哈希表 <code>visited</code> 来记录已被访问的顶点,以避免重复访问顶点。</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-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>
|
||||||
|
|
|
@ -1637,6 +1637,8 @@
|
||||||
<h2 id="622">6.2.2. 链式地址<a class="headerlink" href="#622" title="Permanent link">¶</a></h2>
|
<h2 id="622">6.2.2. 链式地址<a class="headerlink" href="#622" title="Permanent link">¶</a></h2>
|
||||||
<p>在原始哈希表中,桶内的每个地址只能存储一个元素(即键值对)。<strong>考虑将单个元素转化成一个链表,将所有冲突元素都存储在一个链表中</strong>。</p>
|
<p>在原始哈希表中,桶内的每个地址只能存储一个元素(即键值对)。<strong>考虑将单个元素转化成一个链表,将所有冲突元素都存储在一个链表中</strong>。</p>
|
||||||
<p><img alt="链式地址" src="../hash_collision.assets/hash_collision_chaining.png" /></p>
|
<p><img alt="链式地址" src="../hash_collision.assets/hash_collision_chaining.png" /></p>
|
||||||
|
<p align="center"> Fig. 链式地址 </p>
|
||||||
|
|
||||||
<p>链式地址下,哈希表操作方法为:</p>
|
<p>链式地址下,哈希表操作方法为:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>查询元素</strong>:先将 key 输入到哈希函数得到桶内索引,即可访问链表头结点,再通过遍历链表查找对应 value 。</li>
|
<li><strong>查询元素</strong>:先将 key 输入到哈希函数得到桶内索引,即可访问链表头结点,再通过遍历链表查找对应 value 。</li>
|
||||||
|
@ -1660,6 +1662,8 @@
|
||||||
<li>若遇到空位,则说明查找键值对不在哈希表中;</li>
|
<li>若遇到空位,则说明查找键值对不在哈希表中;</li>
|
||||||
</ol>
|
</ol>
|
||||||
<p><img alt="线性探测" src="../hash_collision.assets/hash_collision_linear_probing.png" /></p>
|
<p><img alt="线性探测" src="../hash_collision.assets/hash_collision_linear_probing.png" /></p>
|
||||||
|
<p align="center"> Fig. 线性探测 </p>
|
||||||
|
|
||||||
<p>线性探测存在以下缺陷:</p>
|
<p>线性探测存在以下缺陷:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>不能直接删除元素</strong>。删除元素会导致桶内出现一个空位,在查找其他元素时,该空位有可能导致程序认为元素不存在(即上述第 <code>2.</code> 种情况)。因此需要借助一个标志位来标记删除元素。</li>
|
<li><strong>不能直接删除元素</strong>。删除元素会导致桶内出现一个空位,在查找其他元素时,该空位有可能导致程序认为元素不存在(即上述第 <code>2.</code> 种情况)。因此需要借助一个标志位来标记删除元素。</li>
|
||||||
|
|
|
@ -1603,6 +1603,8 @@
|
||||||
<p>哈希表通过建立「键 key」和「值 value」之间的映射,实现高效的元素查找。具体地,输入一个 key ,在哈希表中查询并获取 value ,时间复杂度为 <span class="arithmatex">\(O(1)\)</span> 。</p>
|
<p>哈希表通过建立「键 key」和「值 value」之间的映射,实现高效的元素查找。具体地,输入一个 key ,在哈希表中查询并获取 value ,时间复杂度为 <span class="arithmatex">\(O(1)\)</span> 。</p>
|
||||||
<p>例如,给定一个包含 <span class="arithmatex">\(n\)</span> 个学生的数据库,每个学生有“姓名 <code>name</code> ”和“学号 <code>id</code> ”两项数据,希望实现一个查询功能:<strong>输入一个学号,返回对应的姓名</strong>,则可以使用哈希表实现。</p>
|
<p>例如,给定一个包含 <span class="arithmatex">\(n\)</span> 个学生的数据库,每个学生有“姓名 <code>name</code> ”和“学号 <code>id</code> ”两项数据,希望实现一个查询功能:<strong>输入一个学号,返回对应的姓名</strong>,则可以使用哈希表实现。</p>
|
||||||
<p><img alt="哈希表的抽象表示" src="../hash_map.assets/hash_map.png" /></p>
|
<p><img alt="哈希表的抽象表示" src="../hash_map.assets/hash_map.png" /></p>
|
||||||
|
<p align="center"> Fig. 哈希表的抽象表示 </p>
|
||||||
|
|
||||||
<h2 id="611">6.1.1. 哈希表效率<a class="headerlink" href="#611" title="Permanent link">¶</a></h2>
|
<h2 id="611">6.1.1. 哈希表效率<a class="headerlink" href="#611" title="Permanent link">¶</a></h2>
|
||||||
<p>除了哈希表之外,还可以使用以下数据结构来实现上述查询功能:</p>
|
<p>除了哈希表之外,还可以使用以下数据结构来实现上述查询功能:</p>
|
||||||
<ol>
|
<ol>
|
||||||
|
@ -1988,6 +1990,8 @@
|
||||||
f(x) = x \% 100
|
f(x) = x \% 100
|
||||||
\]</div>
|
\]</div>
|
||||||
<p><img alt="简单哈希函数示例" src="../hash_map.assets/hash_function.png" /></p>
|
<p><img alt="简单哈希函数示例" src="../hash_map.assets/hash_function.png" /></p>
|
||||||
|
<p align="center"> Fig. 简单哈希函数示例 </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-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-content">
|
||||||
<div class="tabbed-block">
|
<div class="tabbed-block">
|
||||||
|
@ -2832,6 +2836,8 @@ f(12836) = f(20336) = 36
|
||||||
\]</div>
|
\]</div>
|
||||||
<p>两个学号指向了同一个姓名,这明显是不对的,我们将这种现象称为「哈希冲突 Hash Collision」。如何避免哈希冲突的问题将被留在下章讨论。</p>
|
<p>两个学号指向了同一个姓名,这明显是不对的,我们将这种现象称为「哈希冲突 Hash Collision」。如何避免哈希冲突的问题将被留在下章讨论。</p>
|
||||||
<p><img alt="哈希冲突示例" src="../hash_map.assets/hash_collision.png" /></p>
|
<p><img alt="哈希冲突示例" src="../hash_map.assets/hash_collision.png" /></p>
|
||||||
|
<p align="center"> Fig. 哈希冲突示例 </p>
|
||||||
|
|
||||||
<p>综上所述,一个优秀的「哈希函数」应该具备以下特性:</p>
|
<p>综上所述,一个优秀的「哈希函数」应该具备以下特性:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>尽量少地发生哈希冲突;</li>
|
<li>尽量少地发生哈希冲突;</li>
|
||||||
|
|
|
@ -1688,6 +1688,8 @@
|
||||||
<li>「小顶堆 Min Heap」,任意结点的值 <span class="arithmatex">\(\leq\)</span> 其子结点的值;</li>
|
<li>「小顶堆 Min Heap」,任意结点的值 <span class="arithmatex">\(\leq\)</span> 其子结点的值;</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p><img alt="小顶堆与大顶堆" src="../heap.assets/min_heap_and_max_heap.png" /></p>
|
<p><img alt="小顶堆与大顶堆" src="../heap.assets/min_heap_and_max_heap.png" /></p>
|
||||||
|
<p align="center"> Fig. 小顶堆与大顶堆 </p>
|
||||||
|
|
||||||
<h2 id="811">8.1.1. 堆术语与性质<a class="headerlink" href="#811" title="Permanent link">¶</a></h2>
|
<h2 id="811">8.1.1. 堆术语与性质<a class="headerlink" href="#811" title="Permanent link">¶</a></h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>由于堆是完全二叉树,因此最底层结点靠左填充,其它层结点皆被填满。</li>
|
<li>由于堆是完全二叉树,因此最底层结点靠左填充,其它层结点皆被填满。</li>
|
||||||
|
@ -1993,6 +1995,8 @@
|
||||||
<p><strong>二叉树指针</strong>。使用数组表示二叉树时,元素代表结点值,索引代表结点在二叉树中的位置,<strong>而结点指针通过索引映射公式来实现</strong>。</p>
|
<p><strong>二叉树指针</strong>。使用数组表示二叉树时,元素代表结点值,索引代表结点在二叉树中的位置,<strong>而结点指针通过索引映射公式来实现</strong>。</p>
|
||||||
<p>具体地,给定索引 <span class="arithmatex">\(i\)</span> ,那么其左子结点索引为 <span class="arithmatex">\(2i + 1\)</span> 、右子结点索引为 <span class="arithmatex">\(2i + 2\)</span> 、父结点索引为 <span class="arithmatex">\((i - 1) / 2\)</span> (向下整除)。当索引越界时,代表空结点或结点不存在。</p>
|
<p>具体地,给定索引 <span class="arithmatex">\(i\)</span> ,那么其左子结点索引为 <span class="arithmatex">\(2i + 1\)</span> 、右子结点索引为 <span class="arithmatex">\(2i + 2\)</span> 、父结点索引为 <span class="arithmatex">\((i - 1) / 2\)</span> (向下整除)。当索引越界时,代表空结点或结点不存在。</p>
|
||||||
<p><img alt="堆的表示与存储" src="../heap.assets/representation_of_heap.png" /></p>
|
<p><img alt="堆的表示与存储" src="../heap.assets/representation_of_heap.png" /></p>
|
||||||
|
<p align="center"> Fig. 堆的表示与存储 </p>
|
||||||
|
|
||||||
<p>我们将索引映射公式封装成函数,以便后续使用。</p>
|
<p>我们将索引映射公式封装成函数,以便后续使用。</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-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-content">
|
||||||
|
@ -3013,6 +3017,8 @@
|
||||||
T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \cdots + 2^{(h-1)}\times1
|
T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \cdots + 2^{(h-1)}\times1
|
||||||
\]</div>
|
\]</div>
|
||||||
<p><img alt="完美二叉树的各层结点数量" src="../heap.assets/heapify_operations_count.png" /></p>
|
<p><img alt="完美二叉树的各层结点数量" src="../heap.assets/heapify_operations_count.png" /></p>
|
||||||
|
<p align="center"> Fig. 完美二叉树的各层结点数量 </p>
|
||||||
|
|
||||||
<p>化简上式需要借助中学的数列知识,先对 <span class="arithmatex">\(T(h)\)</span> 乘以 <span class="arithmatex">\(2\)</span> ,易得</p>
|
<p>化简上式需要借助中学的数列知识,先对 <span class="arithmatex">\(T(h)\)</span> 乘以 <span class="arithmatex">\(2\)</span> ,易得</p>
|
||||||
<div class="arithmatex">\[
|
<div class="arithmatex">\[
|
||||||
\begin{aligned}
|
\begin{aligned}
|
||||||
|
|
|
@ -1610,6 +1610,8 @@
|
||||||
<li>算法有对应最优的数据结构。给定算法,一般可基于不同的数据结构实现,而最终执行效率往往相差很大。</li>
|
<li>算法有对应最优的数据结构。给定算法,一般可基于不同的数据结构实现,而最终执行效率往往相差很大。</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p><img alt="数据结构与算法的关系" src="../what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png" /></p>
|
<p><img alt="数据结构与算法的关系" src="../what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png" /></p>
|
||||||
|
<p align="center"> Fig. 数据结构与算法的关系 </p>
|
||||||
|
|
||||||
<p>如果将「LEGO 乐高」类比到「数据结构与算法」,那么可以得到下表所示的对应关系。</p>
|
<p>如果将「LEGO 乐高」类比到「数据结构与算法」,那么可以得到下表所示的对应关系。</p>
|
||||||
<div class="center-table">
|
<div class="center-table">
|
||||||
<table>
|
<table>
|
||||||
|
|
|
@ -1694,6 +1694,8 @@
|
||||||
<h2 id="012">0.1.2. 内容结构<a class="headerlink" href="#012" title="Permanent link">¶</a></h2>
|
<h2 id="012">0.1.2. 内容结构<a class="headerlink" href="#012" title="Permanent link">¶</a></h2>
|
||||||
<p>本书主要内容分为复杂度分析、数据结构、算法三个部分。</p>
|
<p>本书主要内容分为复杂度分析、数据结构、算法三个部分。</p>
|
||||||
<p><img alt="Hello 算法内容结构" src="../about_the_book.assets/hello_algo_mindmap.png" /></p>
|
<p><img alt="Hello 算法内容结构" src="../about_the_book.assets/hello_algo_mindmap.png" /></p>
|
||||||
|
<p align="center"> Fig. Hello 算法内容结构 </p>
|
||||||
|
|
||||||
<h3 id="_1">复杂度分析<a class="headerlink" href="#_1" title="Permanent link">¶</a></h3>
|
<h3 id="_1">复杂度分析<a class="headerlink" href="#_1" title="Permanent link">¶</a></h3>
|
||||||
<p>首先介绍数据结构与算法的评价维度、算法效率的评估方法,引出了计算复杂度概念。</p>
|
<p>首先介绍数据结构与算法的评价维度、算法效率的评估方法,引出了计算复杂度概念。</p>
|
||||||
<p>接下来,从 <strong>函数渐近上界</strong> 入手,分别介绍了 <strong>时间复杂度</strong> 和 <strong>空间复杂度</strong>,包括推算方法、常见类型、示例等。同时,剖析了 <strong>最差、最佳、平均</strong> 时间复杂度的联系与区别。</p>
|
<p>接下来,从 <strong>函数渐近上界</strong> 入手,分别介绍了 <strong>时间复杂度</strong> 和 <strong>空间复杂度</strong>,包括推算方法、常见类型、示例等。同时,剖析了 <strong>最差、最佳、平均</strong> 时间复杂度的联系与区别。</p>
|
||||||
|
@ -1727,6 +1729,8 @@
|
||||||
</ul>
|
</ul>
|
||||||
<p>根据观察,很多同学都是从“第二阶段”开始学习算法的。而作为入门教程,<strong>本书内容主要对应“第一阶段”</strong>,致力于帮助读者更高效地开展第二、三阶段的学习。</p>
|
<p>根据观察,很多同学都是从“第二阶段”开始学习算法的。而作为入门教程,<strong>本书内容主要对应“第一阶段”</strong>,致力于帮助读者更高效地开展第二、三阶段的学习。</p>
|
||||||
<p><img alt="算法学习路线" src="../suggestions.assets/learning_route.png" /></p>
|
<p><img alt="算法学习路线" src="../suggestions.assets/learning_route.png" /></p>
|
||||||
|
<p align="center"> Fig. 算法学习路线 </p>
|
||||||
|
|
||||||
<h2 id="014">0.1.4. 本书特点<a class="headerlink" href="#014" title="Permanent link">¶</a></h2>
|
<h2 id="014">0.1.4. 本书特点<a class="headerlink" href="#014" title="Permanent link">¶</a></h2>
|
||||||
<p><strong>以实践为主</strong>。我们知道,学习英语期间光啃书本是远远不够的,需要多听、多说、多写,在实践中培养语感、积累经验。编程语言也是一门语言,因此学习方法也应是类似的,需要多看优秀代码、多敲键盘、多思考代码逻辑。</p>
|
<p><strong>以实践为主</strong>。我们知道,学习英语期间光啃书本是远远不够的,需要多听、多说、多写,在实践中培养语感、积累经验。编程语言也是一门语言,因此学习方法也应是类似的,需要多看优秀代码、多敲键盘、多思考代码逻辑。</p>
|
||||||
<p>本书的理论部分占少量篇幅,主要分为两类:一是基础且必要的概念知识,以培养读者对于算法的感性认识;二是重要的分类、对比或总结,这是为了帮助你站在更高视角俯瞰各个知识点,形成连点成面的效果。</p>
|
<p>本书的理论部分占少量篇幅,主要分为两类:一是基础且必要的概念知识,以培养读者对于算法的感性认识;二是重要的分类、对比或总结,这是为了帮助你站在更高视角俯瞰各个知识点,形成连点成面的效果。</p>
|
||||||
|
|
|
@ -1599,6 +1599,8 @@
|
||||||
<li>在页面底部填写更改说明,然后单击“Propose file change”按钮;页面跳转后,点击“Create pull request”按钮发起拉取请求即可。</li>
|
<li>在页面底部填写更改说明,然后单击“Propose file change”按钮;页面跳转后,点击“Create pull request”按钮发起拉取请求即可。</li>
|
||||||
</ol>
|
</ol>
|
||||||
<p><img alt="页面编辑按键" src="../contribution.assets/edit_markdown.png" /></p>
|
<p><img alt="页面编辑按键" src="../contribution.assets/edit_markdown.png" /></p>
|
||||||
|
<p align="center"> Fig. 页面编辑按键 </p>
|
||||||
|
|
||||||
<p>图片无法直接修改,需要通过新建 <a href="https://github.com/krahets/hello-algo/issues">Issue</a> 或评论留言来描述图片问题,我会第一时间重新画图并替换图片。</p>
|
<p>图片无法直接修改,需要通过新建 <a href="https://github.com/krahets/hello-algo/issues">Issue</a> 或评论留言来描述图片问题,我会第一时间重新画图并替换图片。</p>
|
||||||
<h2 id="042">0.4.2. 内容创作<a class="headerlink" href="#042" title="Permanent link">¶</a></h2>
|
<h2 id="042">0.4.2. 内容创作<a class="headerlink" href="#042" title="Permanent link">¶</a></h2>
|
||||||
<p>如果您想要参与本开源项目,包括翻译代码至其他编程语言、拓展文章内容等,那么需要实施 Pull Request 工作流程:</p>
|
<p>如果您想要参与本开源项目,包括翻译代码至其他编程语言、拓展文章内容等,那么需要实施 Pull Request 工作流程:</p>
|
||||||
|
|
|
@ -1777,6 +1777,8 @@
|
||||||
<p>视频和图片相比于文字的信息密度和结构化程度更高,更容易理解。在本书中,<strong>知识重难点会主要以动画、图解的形式呈现</strong>,而文字的作用则是作为动画和图的解释与补充。</p>
|
<p>视频和图片相比于文字的信息密度和结构化程度更高,更容易理解。在本书中,<strong>知识重难点会主要以动画、图解的形式呈现</strong>,而文字的作用则是作为动画和图的解释与补充。</p>
|
||||||
<p>阅读本书时,若发现某段内容提供了动画或图解,<strong>建议你以图为主线</strong>,将文字内容(一般在图的上方)对齐到图中内容,综合来理解。</p>
|
<p>阅读本书时,若发现某段内容提供了动画或图解,<strong>建议你以图为主线</strong>,将文字内容(一般在图的上方)对齐到图中内容,综合来理解。</p>
|
||||||
<p><img alt="动画图解示例" src="../suggestions.assets/animation.gif" /></p>
|
<p><img alt="动画图解示例" src="../suggestions.assets/animation.gif" /></p>
|
||||||
|
<p align="center"> Fig. 动画图解示例 </p>
|
||||||
|
|
||||||
<h2 id="023">0.2.3. 在代码实践中加深理解<a class="headerlink" href="#023" title="Permanent link">¶</a></h2>
|
<h2 id="023">0.2.3. 在代码实践中加深理解<a class="headerlink" href="#023" title="Permanent link">¶</a></h2>
|
||||||
<p>本书的配套代码托管在<a href="https://github.com/krahets/hello-algo">GitHub 仓库</a>,<strong>源代码包含详细注释,配有测试样例,可以直接运行</strong>。</p>
|
<p>本书的配套代码托管在<a href="https://github.com/krahets/hello-algo">GitHub 仓库</a>,<strong>源代码包含详细注释,配有测试样例,可以直接运行</strong>。</p>
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -1791,15 +1793,22 @@
|
||||||
</code></pre></div>
|
</code></pre></div>
|
||||||
<p>当然,你也可以点击“Download ZIP”直接下载代码压缩包,解压即可。</p>
|
<p>当然,你也可以点击“Download ZIP”直接下载代码压缩包,解压即可。</p>
|
||||||
<p><img alt="克隆仓库与下载代码" src="../suggestions.assets/download_code.png" /></p>
|
<p><img alt="克隆仓库与下载代码" src="../suggestions.assets/download_code.png" /></p>
|
||||||
|
<p align="center"> Fig. 克隆仓库与下载代码 </p>
|
||||||
|
|
||||||
<h3 id="3">3) 运行源代码<a class="headerlink" href="#3" title="Permanent link">¶</a></h3>
|
<h3 id="3">3) 运行源代码<a class="headerlink" href="#3" title="Permanent link">¶</a></h3>
|
||||||
<p>若代码块的顶部标有文件名称,则可在仓库 <code>codes</code> 文件夹中找到对应的 <strong>源代码文件</strong>。</p>
|
<p>若代码块的顶部标有文件名称,则可在仓库 <code>codes</code> 文件夹中找到对应的 <strong>源代码文件</strong>。</p>
|
||||||
<p><img alt="代码块与对应的源代码文件" src="../suggestions.assets/code_md_to_repo.png" /></p>
|
<p><img alt="代码块与对应的源代码文件" src="../suggestions.assets/code_md_to_repo.png" /></p>
|
||||||
|
<p align="center"> Fig. 代码块与对应的源代码文件 </p>
|
||||||
|
|
||||||
<p>源代码文件可以帮助你省去不必要的调试时间,将精力集中在学习内容上。</p>
|
<p>源代码文件可以帮助你省去不必要的调试时间,将精力集中在学习内容上。</p>
|
||||||
<p><img alt="运行代码示例" src="../suggestions.assets/running_code.gif" /></p>
|
<p><img alt="运行代码示例" src="../suggestions.assets/running_code.gif" /></p>
|
||||||
|
<p align="center"> Fig. 运行代码示例 </p>
|
||||||
|
|
||||||
<h2 id="024">0.2.4. 在提问讨论中共同成长<a class="headerlink" href="#024" title="Permanent link">¶</a></h2>
|
<h2 id="024">0.2.4. 在提问讨论中共同成长<a class="headerlink" href="#024" title="Permanent link">¶</a></h2>
|
||||||
<p>阅读本书时,请不要“惯着”那些弄不明白的知识点。<strong>欢迎在评论区留下你的问题</strong>,小伙伴们和我都会给予解答,您一般 2 日内会得到回复。</p>
|
<p>阅读本书时,请不要“惯着”那些弄不明白的知识点。<strong>欢迎在评论区留下你的问题</strong>,小伙伴们和我都会给予解答,您一般 2 日内会得到回复。</p>
|
||||||
<p>同时,也希望你可以多花时间逛逛评论区。一方面,可以看看大家遇到了什么问题,反过来查漏补缺,这往往可以引起更加深度的思考。另一方面,也希望你可以慷慨地解答小伙伴们的问题、分享自己的见解,大家互相学习与进步!</p>
|
<p>同时,也希望你可以多花时间逛逛评论区。一方面,可以看看大家遇到了什么问题,反过来查漏补缺,这往往可以引起更加深度的思考。另一方面,也希望你可以慷慨地解答小伙伴们的问题、分享自己的见解,大家互相学习与进步!</p>
|
||||||
<p><img alt="评论区示例" src="../suggestions.assets/comment.gif" /></p>
|
<p><img alt="评论区示例" src="../suggestions.assets/comment.gif" /></p>
|
||||||
|
<p align="center"> Fig. 评论区示例 </p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1594,6 +1594,8 @@
|
||||||
<h2 id="1031">10.3.1. 算法实现<a class="headerlink" href="#1031" title="Permanent link">¶</a></h2>
|
<h2 id="1031">10.3.1. 算法实现<a class="headerlink" href="#1031" title="Permanent link">¶</a></h2>
|
||||||
<p>如果我们想要给定数组中的一个目标元素 <code>target</code> ,获取该元素的索引,那么可以借助一个哈希表实现查找。</p>
|
<p>如果我们想要给定数组中的一个目标元素 <code>target</code> ,获取该元素的索引,那么可以借助一个哈希表实现查找。</p>
|
||||||
<p><img alt="哈希查找数组索引" src="../hashing_search.assets/hash_search_index.png" /></p>
|
<p><img alt="哈希查找数组索引" src="../hashing_search.assets/hash_search_index.png" /></p>
|
||||||
|
<p align="center"> Fig. 哈希查找数组索引 </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-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-content">
|
||||||
<div class="tabbed-block">
|
<div class="tabbed-block">
|
||||||
|
@ -1692,6 +1694,8 @@
|
||||||
</div>
|
</div>
|
||||||
<p>再比如,如果我们想要给定一个目标结点值 <code>target</code> ,获取对应的链表结点对象,那么也可以使用哈希查找实现。</p>
|
<p>再比如,如果我们想要给定一个目标结点值 <code>target</code> ,获取对应的链表结点对象,那么也可以使用哈希查找实现。</p>
|
||||||
<p><img alt="哈希查找链表结点" src="../hashing_search.assets/hash_search_listnode.png" /></p>
|
<p><img alt="哈希查找链表结点" src="../hashing_search.assets/hash_search_listnode.png" /></p>
|
||||||
|
<p align="center"> Fig. 哈希查找链表结点 </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-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-content">
|
||||||
<div class="tabbed-block">
|
<div class="tabbed-block">
|
||||||
|
|
|
@ -1590,6 +1590,8 @@
|
||||||
<h2 id="1011">10.1.1. 算法实现<a class="headerlink" href="#1011" title="Permanent link">¶</a></h2>
|
<h2 id="1011">10.1.1. 算法实现<a class="headerlink" href="#1011" title="Permanent link">¶</a></h2>
|
||||||
<p>线性查找实质上就是遍历数据结构 + 判断条件。比如,我们想要在数组 <code>nums</code> 中查找目标元素 <code>target</code> 的对应索引,那么可以在数组中进行线性查找。</p>
|
<p>线性查找实质上就是遍历数据结构 + 判断条件。比如,我们想要在数组 <code>nums</code> 中查找目标元素 <code>target</code> 的对应索引,那么可以在数组中进行线性查找。</p>
|
||||||
<p><img alt="在数组中线性查找元素" src="../linear_search.assets/linear_search.png" /></p>
|
<p><img alt="在数组中线性查找元素" src="../linear_search.assets/linear_search.png" /></p>
|
||||||
|
<p align="center"> Fig. 在数组中线性查找元素 </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-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-content">
|
||||||
<div class="tabbed-block">
|
<div class="tabbed-block">
|
||||||
|
|
|
@ -1625,6 +1625,8 @@
|
||||||
<li>以此类推…… <strong>循环 <span class="arithmatex">\(n - 1\)</span> 轮「冒泡」,即可完成整个数组的排序</strong>。</li>
|
<li>以此类推…… <strong>循环 <span class="arithmatex">\(n - 1\)</span> 轮「冒泡」,即可完成整个数组的排序</strong>。</li>
|
||||||
</ol>
|
</ol>
|
||||||
<p><img alt="冒泡排序流程" src="../bubble_sort.assets/bubble_sort_overview.png" /></p>
|
<p><img alt="冒泡排序流程" src="../bubble_sort.assets/bubble_sort_overview.png" /></p>
|
||||||
|
<p align="center"> Fig. 冒泡排序流程 </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-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-content">
|
||||||
<div class="tabbed-block">
|
<div class="tabbed-block">
|
||||||
|
|
|
@ -1590,6 +1590,8 @@
|
||||||
<p>「插入操作」原理:选定某个待排序元素为基准数 <code>base</code>,将 <code>base</code> 与其左侧已排序区间元素依次对比大小,并插入到正确位置。</p>
|
<p>「插入操作」原理:选定某个待排序元素为基准数 <code>base</code>,将 <code>base</code> 与其左侧已排序区间元素依次对比大小,并插入到正确位置。</p>
|
||||||
<p>回忆数组插入操作,我们需要将从目标索引到 <code>base</code> 之间的所有元素向右移动一位,然后再将 <code>base</code> 赋值给目标索引。</p>
|
<p>回忆数组插入操作,我们需要将从目标索引到 <code>base</code> 之间的所有元素向右移动一位,然后再将 <code>base</code> 赋值给目标索引。</p>
|
||||||
<p><img alt="单次插入操作" src="../insertion_sort.assets/insertion_operation.png" /></p>
|
<p><img alt="单次插入操作" src="../insertion_sort.assets/insertion_operation.png" /></p>
|
||||||
|
<p align="center"> Fig. 单次插入操作 </p>
|
||||||
|
|
||||||
<h2 id="1131">11.3.1. 算法流程<a class="headerlink" href="#1131" title="Permanent link">¶</a></h2>
|
<h2 id="1131">11.3.1. 算法流程<a class="headerlink" href="#1131" title="Permanent link">¶</a></h2>
|
||||||
<ol>
|
<ol>
|
||||||
<li>第 1 轮先选取数组的 <strong>第 2 个元素</strong> 为 <code>base</code> ,执行「插入操作」后,<strong>数组前 2 个元素已完成排序</strong>。</li>
|
<li>第 1 轮先选取数组的 <strong>第 2 个元素</strong> 为 <code>base</code> ,执行「插入操作」后,<strong>数组前 2 个元素已完成排序</strong>。</li>
|
||||||
|
@ -1597,6 +1599,8 @@
|
||||||
<li>以此类推……最后一轮选取 <strong>数组尾元素</strong> 为 <code>base</code> ,执行「插入操作」后,<strong>所有元素已完成排序</strong>。</li>
|
<li>以此类推……最后一轮选取 <strong>数组尾元素</strong> 为 <code>base</code> ,执行「插入操作」后,<strong>所有元素已完成排序</strong>。</li>
|
||||||
</ol>
|
</ol>
|
||||||
<p><img alt="插入排序流程" src="../insertion_sort.assets/insertion_sort_overview.png" /></p>
|
<p><img alt="插入排序流程" src="../insertion_sort.assets/insertion_sort_overview.png" /></p>
|
||||||
|
<p align="center"> Fig. 插入排序流程 </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-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-content">
|
||||||
<div class="tabbed-block">
|
<div class="tabbed-block">
|
||||||
|
|
|
@ -1646,6 +1646,8 @@
|
||||||
<li>排序算法可以根据需要设定 <strong>判断规则</strong>,例如数字大小、字符 ASCII 码顺序、自定义规则;</li>
|
<li>排序算法可以根据需要设定 <strong>判断规则</strong>,例如数字大小、字符 ASCII 码顺序、自定义规则;</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p><img alt="排序中不同的元素类型和判断规则" src="../intro_to_sort.assets/sorting_examples.png" /></p>
|
<p><img alt="排序中不同的元素类型和判断规则" src="../intro_to_sort.assets/sorting_examples.png" /></p>
|
||||||
|
<p align="center"> Fig. 排序中不同的元素类型和判断规则 </p>
|
||||||
|
|
||||||
<h2 id="1111">11.1.1. 评价维度<a class="headerlink" href="#1111" title="Permanent link">¶</a></h2>
|
<h2 id="1111">11.1.1. 评价维度<a class="headerlink" href="#1111" title="Permanent link">¶</a></h2>
|
||||||
<p>排序算法主要可根据 <strong>稳定性 、就地性 、自适应性 、比较类</strong> 来分类。</p>
|
<p>排序算法主要可根据 <strong>稳定性 、就地性 、自适应性 、比较类</strong> 来分类。</p>
|
||||||
<h3 id="_1">稳定性<a class="headerlink" href="#_1" title="Permanent link">¶</a></h3>
|
<h3 id="_1">稳定性<a class="headerlink" href="#_1" title="Permanent link">¶</a></h3>
|
||||||
|
|
|
@ -1592,6 +1592,8 @@
|
||||||
<li><strong>合并阶段</strong>:划分到子数组长度为 1 时,开始向上合并,不断将 <strong>左、右两个短排序数组</strong> 合并为 <strong>一个长排序数组</strong>,直至合并至原数组时完成排序;</li>
|
<li><strong>合并阶段</strong>:划分到子数组长度为 1 时,开始向上合并,不断将 <strong>左、右两个短排序数组</strong> 合并为 <strong>一个长排序数组</strong>,直至合并至原数组时完成排序;</li>
|
||||||
</ol>
|
</ol>
|
||||||
<p><img alt="归并排序的划分与合并阶段" src="../merge_sort.assets/merge_sort_overview.png" /></p>
|
<p><img alt="归并排序的划分与合并阶段" src="../merge_sort.assets/merge_sort_overview.png" /></p>
|
||||||
|
<p align="center"> Fig. 归并排序的划分与合并阶段 </p>
|
||||||
|
|
||||||
<h2 id="1151">11.5.1. 算法流程<a class="headerlink" href="#1151" title="Permanent link">¶</a></h2>
|
<h2 id="1151">11.5.1. 算法流程<a class="headerlink" href="#1151" title="Permanent link">¶</a></h2>
|
||||||
<p><strong>「递归划分」</strong> 从顶至底递归地 <strong>将数组从中点切为两个子数组</strong>,直至长度为 1 ;</p>
|
<p><strong>「递归划分」</strong> 从顶至底递归地 <strong>将数组从中点切为两个子数组</strong>,直至长度为 1 ;</p>
|
||||||
<ol>
|
<ol>
|
||||||
|
|
|
@ -1886,6 +1886,8 @@
|
||||||
</ol>
|
</ol>
|
||||||
<p>观察发现,快速排序和「二分查找」的原理类似,都是以对数阶的时间复杂度来缩小处理区间。</p>
|
<p>观察发现,快速排序和「二分查找」的原理类似,都是以对数阶的时间复杂度来缩小处理区间。</p>
|
||||||
<p><img alt="快速排序流程" src="../quick_sort.assets/quick_sort_overview.png" /></p>
|
<p><img alt="快速排序流程" src="../quick_sort.assets/quick_sort_overview.png" /></p>
|
||||||
|
<p align="center"> Fig. 快速排序流程 </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-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-content">
|
||||||
<div class="tabbed-block">
|
<div class="tabbed-block">
|
||||||
|
|
|
@ -1614,6 +1614,8 @@
|
||||||
<h1 id="53">5.3. 双向队列<a class="headerlink" href="#53" title="Permanent link">¶</a></h1>
|
<h1 id="53">5.3. 双向队列<a class="headerlink" href="#53" title="Permanent link">¶</a></h1>
|
||||||
<p>对于队列,我们只能在头部删除或在尾部添加元素,而「双向队列 Deque」更加灵活,在其头部和尾部都能执行元素添加或删除操作。</p>
|
<p>对于队列,我们只能在头部删除或在尾部添加元素,而「双向队列 Deque」更加灵活,在其头部和尾部都能执行元素添加或删除操作。</p>
|
||||||
<p><img alt="双向队列的操作" src="../deque.assets/deque_operations.png" /></p>
|
<p><img alt="双向队列的操作" src="../deque.assets/deque_operations.png" /></p>
|
||||||
|
<p align="center"> Fig. 双向队列的操作 </p>
|
||||||
|
|
||||||
<h2 id="531">5.3.1. 双向队列常用操作<a class="headerlink" href="#531" title="Permanent link">¶</a></h2>
|
<h2 id="531">5.3.1. 双向队列常用操作<a class="headerlink" href="#531" title="Permanent link">¶</a></h2>
|
||||||
<p>双向队列的常用操作见下表,方法名需根据特定语言来确定。</p>
|
<p>双向队列的常用操作见下表,方法名需根据特定语言来确定。</p>
|
||||||
<div class="center-table">
|
<div class="center-table">
|
||||||
|
|
|
@ -1643,6 +1643,8 @@
|
||||||
<p>「队列 Queue」是一种遵循「先入先出 first in, first out」数据操作规则的线性数据结构。顾名思义,队列模拟的是排队现象,即外面的人不断加入队列尾部,而处于队列头部的人不断地离开。</p>
|
<p>「队列 Queue」是一种遵循「先入先出 first in, first out」数据操作规则的线性数据结构。顾名思义,队列模拟的是排队现象,即外面的人不断加入队列尾部,而处于队列头部的人不断地离开。</p>
|
||||||
<p>我们将队列头部称为「队首」,队列尾部称为「队尾」,将把元素加入队尾的操作称为「入队」,删除队首元素的操作称为「出队」。</p>
|
<p>我们将队列头部称为「队首」,队列尾部称为「队尾」,将把元素加入队尾的操作称为「入队」,删除队首元素的操作称为「出队」。</p>
|
||||||
<p><img alt="队列的先入先出规则" src="../queue.assets/queue_operations.png" /></p>
|
<p><img alt="队列的先入先出规则" src="../queue.assets/queue_operations.png" /></p>
|
||||||
|
<p align="center"> Fig. 队列的先入先出规则 </p>
|
||||||
|
|
||||||
<h2 id="521">5.2.1. 队列常用操作<a class="headerlink" href="#521" title="Permanent link">¶</a></h2>
|
<h2 id="521">5.2.1. 队列常用操作<a class="headerlink" href="#521" title="Permanent link">¶</a></h2>
|
||||||
<p>队列的常用操作见下表,方法名需根据特定语言来确定。</p>
|
<p>队列的常用操作见下表,方法名需根据特定语言来确定。</p>
|
||||||
<div class="center-table">
|
<div class="center-table">
|
||||||
|
|
|
@ -1698,6 +1698,8 @@
|
||||||
<p>“盘子”是一种形象比喻,我们将盘子替换为任意一种元素(例如整数、字符、对象等),就得到了栈数据结构。</p>
|
<p>“盘子”是一种形象比喻,我们将盘子替换为任意一种元素(例如整数、字符、对象等),就得到了栈数据结构。</p>
|
||||||
<p>我们将这一摞元素的顶部称为「栈顶」,将底部称为「栈底」,将把元素添加到栈顶的操作称为「入栈」,将删除栈顶元素的操作称为「出栈」。</p>
|
<p>我们将这一摞元素的顶部称为「栈顶」,将底部称为「栈底」,将把元素添加到栈顶的操作称为「入栈」,将删除栈顶元素的操作称为「出栈」。</p>
|
||||||
<p><img alt="栈的先入后出规则" src="../stack.assets/stack_operations.png" /></p>
|
<p><img alt="栈的先入后出规则" src="../stack.assets/stack_operations.png" /></p>
|
||||||
|
<p align="center"> Fig. 栈的先入后出规则 </p>
|
||||||
|
|
||||||
<h2 id="511">5.1.1. 栈常用操作<a class="headerlink" href="#511" title="Permanent link">¶</a></h2>
|
<h2 id="511">5.1.1. 栈常用操作<a class="headerlink" href="#511" title="Permanent link">¶</a></h2>
|
||||||
<p>栈的常用操作见下表(方法命名以 Java 为例)。</p>
|
<p>栈的常用操作见下表(方法命名以 Java 为例)。</p>
|
||||||
<div class="center-table">
|
<div class="center-table">
|
||||||
|
|
|
@ -1779,8 +1779,12 @@
|
||||||
<p>在「二叉搜索树」章节中提到,在进行多次插入与删除操作后,二叉搜索树可能会退化为链表。此时所有操作的时间复杂度都会由 <span class="arithmatex">\(O(\log n)\)</span> 劣化至 <span class="arithmatex">\(O(n)\)</span> 。</p>
|
<p>在「二叉搜索树」章节中提到,在进行多次插入与删除操作后,二叉搜索树可能会退化为链表。此时所有操作的时间复杂度都会由 <span class="arithmatex">\(O(\log n)\)</span> 劣化至 <span class="arithmatex">\(O(n)\)</span> 。</p>
|
||||||
<p>如下图所示,执行两步删除结点后,该二叉搜索树就会退化为链表。</p>
|
<p>如下图所示,执行两步删除结点后,该二叉搜索树就会退化为链表。</p>
|
||||||
<p><img alt="AVL 树在删除结点后发生退化" src="../avl_tree.assets/avltree_degradation_from_removing_node.png" /></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><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>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>换言之,在频繁增删查改的使用场景中,AVL 树可始终保持很高的数据增删查改效率,具有很好的应用价值。</p>
|
||||||
<h2 id="741-avl">7.4.1. AVL 树常见术语<a class="headerlink" href="#741-avl" title="Permanent link">¶</a></h2>
|
<h2 id="741-avl">7.4.1. AVL 树常见术语<a class="headerlink" href="#741-avl" title="Permanent link">¶</a></h2>
|
||||||
|
@ -2177,6 +2181,8 @@
|
||||||
</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><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-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-content">
|
||||||
|
@ -2333,8 +2339,12 @@
|
||||||
<h3 id="case-2-">Case 2 - 左旋<a class="headerlink" href="#case-2-" title="Permanent link">¶</a></h3>
|
<h3 id="case-2-">Case 2 - 左旋<a class="headerlink" href="#case-2-" title="Permanent link">¶</a></h3>
|
||||||
<p>类似地,如果将取上述失衡二叉树的“镜像”,那么则需要「左旋」操作。</p>
|
<p>类似地,如果将取上述失衡二叉树的“镜像”,那么则需要「左旋」操作。</p>
|
||||||
<p><img alt="左旋操作" src="../avl_tree.assets/avltree_left_rotate.png" /></p>
|
<p><img alt="左旋操作" src="../avl_tree.assets/avltree_left_rotate.png" /></p>
|
||||||
|
<p align="center"> Fig. 左旋操作 </p>
|
||||||
|
|
||||||
<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_left_rotate_with_grandchild.png" /></p>
|
<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-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-content">
|
||||||
|
@ -2491,12 +2501,18 @@
|
||||||
<h3 id="case-3-">Case 3 - 先左后右<a class="headerlink" href="#case-3-" title="Permanent link">¶</a></h3>
|
<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>
|
<p>对于下图的失衡结点 3 ,<strong>单一使用左旋或右旋都无法使子树恢复平衡</strong>,此时需要「先左旋后右旋」,即先对 <code>child</code> 执行「左旋」,再对 <code>node</code> 执行「右旋」。</p>
|
||||||
<p><img alt="先左旋后右旋" src="../avl_tree.assets/avltree_left_right_rotate.png" /></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>
|
<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>
|
<p>同理,取以上失衡二叉树的镜像,则需要「先右旋后左旋」,即先对 <code>child</code> 执行「右旋」,然后对 <code>node</code> 执行「左旋」。</p>
|
||||||
<p><img alt="先右旋后左旋" src="../avl_tree.assets/avltree_right_left_rotate.png" /></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>
|
<h3 id="_3">旋转的选择<a class="headerlink" href="#_3" title="Permanent link">¶</a></h3>
|
||||||
<p>下图描述的四种失衡情况与上述 Cases 逐个对应,分别需采用 <strong>右旋、左旋、先右后左、先左后右</strong> 的旋转操作。</p>
|
<p>下图描述的四种失衡情况与上述 Cases 逐个对应,分别需采用 <strong>右旋、左旋、先右后左、先左后右</strong> 的旋转操作。</p>
|
||||||
<p><img alt="AVL 树的四种旋转情况" src="../avl_tree.assets/avltree_rotation_cases.png" /></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>具体地,在代码中使用 <strong>失衡结点的平衡因子、较高一侧子结点的平衡因子</strong> 来确定失衡结点属于上图中的哪种情况。</p>
|
||||||
<div class="center-table">
|
<div class="center-table">
|
||||||
<table>
|
<table>
|
||||||
|
|
|
@ -1674,6 +1674,8 @@
|
||||||
<li>任意结点的左子树和右子树也是二叉搜索树,即也满足条件 <code>1.</code> ;</li>
|
<li>任意结点的左子树和右子树也是二叉搜索树,即也满足条件 <code>1.</code> ;</li>
|
||||||
</ol>
|
</ol>
|
||||||
<p><img alt="二叉搜索树" src="../binary_search_tree.assets/binary_search_tree.png" /></p>
|
<p><img alt="二叉搜索树" src="../binary_search_tree.assets/binary_search_tree.png" /></p>
|
||||||
|
<p align="center"> Fig. 二叉搜索树 </p>
|
||||||
|
|
||||||
<h2 id="731">7.3.1. 二叉搜索树的操作<a class="headerlink" href="#731" title="Permanent link">¶</a></h2>
|
<h2 id="731">7.3.1. 二叉搜索树的操作<a class="headerlink" href="#731" title="Permanent link">¶</a></h2>
|
||||||
<h3 id="_1">查找结点<a class="headerlink" href="#_1" title="Permanent link">¶</a></h3>
|
<h3 id="_1">查找结点<a class="headerlink" href="#_1" title="Permanent link">¶</a></h3>
|
||||||
<p>给定目标结点值 <code>num</code> ,可以根据二叉搜索树的性质来查找。我们声明一个结点 <code>cur</code> ,从二叉树的根结点 <code>root</code> 出发,循环比较结点值 <code>cur.val</code> 和 <code>num</code> 之间的大小关系</p>
|
<p>给定目标结点值 <code>num</code> ,可以根据二叉搜索树的性质来查找。我们声明一个结点 <code>cur</code> ,从二叉树的根结点 <code>root</code> 出发,循环比较结点值 <code>cur.val</code> 和 <code>num</code> 之间的大小关系</p>
|
||||||
|
@ -1894,6 +1896,8 @@
|
||||||
</ol>
|
</ol>
|
||||||
<p>二叉搜索树不允许存在重复结点,否则将会违背其定义。因此若待插入结点在树中已经存在,则不执行插入,直接返回即可。</p>
|
<p>二叉搜索树不允许存在重复结点,否则将会违背其定义。因此若待插入结点在树中已经存在,则不执行插入,直接返回即可。</p>
|
||||||
<p><img alt="在二叉搜索树中插入结点" src="../binary_search_tree.assets/bst_insert.png" /></p>
|
<p><img alt="在二叉搜索树中插入结点" src="../binary_search_tree.assets/bst_insert.png" /></p>
|
||||||
|
<p align="center"> Fig. 在二叉搜索树中插入结点 </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-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-content">
|
||||||
<div class="tabbed-block">
|
<div class="tabbed-block">
|
||||||
|
@ -2174,8 +2178,12 @@
|
||||||
<p>与插入结点一样,我们需要在删除操作后维持二叉搜索树的“左子树 < 根结点 < 右子树”的性质。首先,我们需要在二叉树中执行查找操作,获取待删除结点。接下来,根据待删除结点的子结点数量,删除操作需要分为三种情况:</p>
|
<p>与插入结点一样,我们需要在删除操作后维持二叉搜索树的“左子树 < 根结点 < 右子树”的性质。首先,我们需要在二叉树中执行查找操作,获取待删除结点。接下来,根据待删除结点的子结点数量,删除操作需要分为三种情况:</p>
|
||||||
<p><strong>当待删除结点的子结点数量 <span class="arithmatex">\(= 0\)</span> 时</strong>,表明待删除结点是叶结点,直接删除即可。</p>
|
<p><strong>当待删除结点的子结点数量 <span class="arithmatex">\(= 0\)</span> 时</strong>,表明待删除结点是叶结点,直接删除即可。</p>
|
||||||
<p><img alt="在二叉搜索树中删除结点(度为 0)" src="../binary_search_tree.assets/bst_remove_case1.png" /></p>
|
<p><img alt="在二叉搜索树中删除结点(度为 0)" src="../binary_search_tree.assets/bst_remove_case1.png" /></p>
|
||||||
|
<p align="center"> Fig. 在二叉搜索树中删除结点(度为 0) </p>
|
||||||
|
|
||||||
<p><strong>当待删除结点的子结点数量 <span class="arithmatex">\(= 1\)</span> 时</strong>,将待删除结点替换为其子结点即可。</p>
|
<p><strong>当待删除结点的子结点数量 <span class="arithmatex">\(= 1\)</span> 时</strong>,将待删除结点替换为其子结点即可。</p>
|
||||||
<p><img alt="在二叉搜索树中删除结点(度为 1)" src="../binary_search_tree.assets/bst_remove_case2.png" /></p>
|
<p><img alt="在二叉搜索树中删除结点(度为 1)" src="../binary_search_tree.assets/bst_remove_case2.png" /></p>
|
||||||
|
<p align="center"> Fig. 在二叉搜索树中删除结点(度为 1) </p>
|
||||||
|
|
||||||
<p><strong>当待删除结点的子结点数量 <span class="arithmatex">\(= 2\)</span> 时</strong>,删除操作分为三步:</p>
|
<p><strong>当待删除结点的子结点数量 <span class="arithmatex">\(= 2\)</span> 时</strong>,删除操作分为三步:</p>
|
||||||
<ol>
|
<ol>
|
||||||
<li>找到待删除结点在 <strong>中序遍历序列</strong> 中的下一个结点,记为 <code>nex</code> ;</li>
|
<li>找到待删除结点在 <strong>中序遍历序列</strong> 中的下一个结点,记为 <code>nex</code> ;</li>
|
||||||
|
@ -2741,6 +2749,8 @@
|
||||||
<p>我们知道,「中序遍历」遵循“左 <span class="arithmatex">\(\rightarrow\)</span> 根 <span class="arithmatex">\(\rightarrow\)</span> 右”的遍历优先级,而二叉搜索树遵循“左子结点 <span class="arithmatex">\(<\)</span> 根结点 <span class="arithmatex">\(<\)</span> 右子结点”的大小关系。因此,在二叉搜索树中进行中序遍历时,总是会优先遍历下一个最小结点,从而得出一条重要性质:<strong>二叉搜索树的中序遍历序列是升序的</strong>。</p>
|
<p>我们知道,「中序遍历」遵循“左 <span class="arithmatex">\(\rightarrow\)</span> 根 <span class="arithmatex">\(\rightarrow\)</span> 右”的遍历优先级,而二叉搜索树遵循“左子结点 <span class="arithmatex">\(<\)</span> 根结点 <span class="arithmatex">\(<\)</span> 右子结点”的大小关系。因此,在二叉搜索树中进行中序遍历时,总是会优先遍历下一个最小结点,从而得出一条重要性质:<strong>二叉搜索树的中序遍历序列是升序的</strong>。</p>
|
||||||
<p>借助中序遍历升序的性质,我们在二叉搜索树中获取有序数据仅需 <span class="arithmatex">\(O(n)\)</span> 时间,而无需额外排序,非常高效。</p>
|
<p>借助中序遍历升序的性质,我们在二叉搜索树中获取有序数据仅需 <span class="arithmatex">\(O(n)\)</span> 时间,而无需额外排序,非常高效。</p>
|
||||||
<p><img alt="二叉搜索树的中序遍历序列" src="../binary_search_tree.assets/bst_inorder_traversal.png" /></p>
|
<p><img alt="二叉搜索树的中序遍历序列" src="../binary_search_tree.assets/bst_inorder_traversal.png" /></p>
|
||||||
|
<p align="center"> Fig. 二叉搜索树的中序遍历序列 </p>
|
||||||
|
|
||||||
<h2 id="732">7.3.2. 二叉搜索树的效率<a class="headerlink" href="#732" title="Permanent link">¶</a></h2>
|
<h2 id="732">7.3.2. 二叉搜索树的效率<a class="headerlink" href="#732" title="Permanent link">¶</a></h2>
|
||||||
<p>假设给定 <span class="arithmatex">\(n\)</span> 个数字,最常用的存储方式是「数组」,那么对于这串乱序的数字,常见操作的效率为:</p>
|
<p>假设给定 <span class="arithmatex">\(n\)</span> 个数字,最常用的存储方式是「数组」,那么对于这串乱序的数字,常见操作的效率为:</p>
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -2803,6 +2813,8 @@
|
||||||
<p>在实际应用中,如何保持二叉搜索树的平衡,也是一个需要重要考虑的问题。</p>
|
<p>在实际应用中,如何保持二叉搜索树的平衡,也是一个需要重要考虑的问题。</p>
|
||||||
</div>
|
</div>
|
||||||
<p><img alt="二叉搜索树的平衡与退化" src="../binary_search_tree.assets/bst_degradation.png" /></p>
|
<p><img alt="二叉搜索树的平衡与退化" src="../binary_search_tree.assets/bst_degradation.png" /></p>
|
||||||
|
<p align="center"> Fig. 二叉搜索树的平衡与退化 </p>
|
||||||
|
|
||||||
<h2 id="734">7.3.4. 二叉搜索树常见应用<a class="headerlink" href="#734" title="Permanent link">¶</a></h2>
|
<h2 id="734">7.3.4. 二叉搜索树常见应用<a class="headerlink" href="#734" title="Permanent link">¶</a></h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>系统中的多级索引,高效查找、插入、删除操作。</li>
|
<li>系统中的多级索引,高效查找、插入、删除操作。</li>
|
||||||
|
|
|
@ -1791,6 +1791,8 @@
|
||||||
<p>结点的两个指针分别指向「左子结点 Left Child Node」和「右子结点 Right Child Node」,并且称该结点为两个子结点的「父结点 Parent Node」。给定二叉树某结点,将左子结点以下的树称为该结点的「左子树 Left Subtree」,右子树同理。</p>
|
<p>结点的两个指针分别指向「左子结点 Left Child Node」和「右子结点 Right Child Node」,并且称该结点为两个子结点的「父结点 Parent Node」。给定二叉树某结点,将左子结点以下的树称为该结点的「左子树 Left Subtree」,右子树同理。</p>
|
||||||
<p>除了叶结点外,每个结点都有子结点和子树。例如,若将下图的「结点 2」看作父结点,那么其左子结点和右子结点分别为「结点 4」和「结点 5」,左子树和右子树分别为「结点 4 及其以下结点形成的树」和「结点 5 及其以下结点形成的树」。</p>
|
<p>除了叶结点外,每个结点都有子结点和子树。例如,若将下图的「结点 2」看作父结点,那么其左子结点和右子结点分别为「结点 4」和「结点 5」,左子树和右子树分别为「结点 4 及其以下结点形成的树」和「结点 5 及其以下结点形成的树」。</p>
|
||||||
<p><img alt="父结点、子结点、子树" src="../binary_tree.assets/binary_tree_definition.png" /></p>
|
<p><img alt="父结点、子结点、子树" src="../binary_tree.assets/binary_tree_definition.png" /></p>
|
||||||
|
<p align="center"> Fig. 父结点、子结点、子树 </p>
|
||||||
|
|
||||||
<h2 id="711">7.1.1. 二叉树常见术语<a class="headerlink" href="#711" title="Permanent link">¶</a></h2>
|
<h2 id="711">7.1.1. 二叉树常见术语<a class="headerlink" href="#711" title="Permanent link">¶</a></h2>
|
||||||
<p>二叉树的术语较多,建议尽量理解并记住。后续可能遗忘,可以在需要使用时回来查看确认。</p>
|
<p>二叉树的术语较多,建议尽量理解并记住。后续可能遗忘,可以在需要使用时回来查看确认。</p>
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -1804,6 +1806,8 @@
|
||||||
<li>结点「高度 Height」:最远叶结点到该结点走过边的数量;</li>
|
<li>结点「高度 Height」:最远叶结点到该结点走过边的数量;</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p><img alt="二叉树的常用术语" src="../binary_tree.assets/binary_tree_terminology.png" /></p>
|
<p><img alt="二叉树的常用术语" src="../binary_tree.assets/binary_tree_terminology.png" /></p>
|
||||||
|
<p align="center"> Fig. 二叉树的常用术语 </p>
|
||||||
|
|
||||||
<div class="admonition tip">
|
<div class="admonition tip">
|
||||||
<p class="admonition-title">高度与深度的定义</p>
|
<p class="admonition-title">高度与深度的定义</p>
|
||||||
<p>值得注意,我们通常将「高度」和「深度」定义为“走过边的数量”,而有些题目或教材会将其定义为“走过结点的数量”,此时高度或深度都需要 + 1 。</p>
|
<p>值得注意,我们通常将「高度」和「深度」定义为“走过边的数量”,而有些题目或教材会将其定义为“走过结点的数量”,此时高度或深度都需要 + 1 。</p>
|
||||||
|
@ -1942,6 +1946,8 @@
|
||||||
</div>
|
</div>
|
||||||
<p><strong>插入与删除结点</strong>。与链表类似,插入与删除结点都可以通过修改指针实现。</p>
|
<p><strong>插入与删除结点</strong>。与链表类似,插入与删除结点都可以通过修改指针实现。</p>
|
||||||
<p><img alt="在二叉树中插入与删除结点" src="../binary_tree.assets/binary_tree_add_remove.png" /></p>
|
<p><img alt="在二叉树中插入与删除结点" src="../binary_tree.assets/binary_tree_add_remove.png" /></p>
|
||||||
|
<p align="center"> Fig. 在二叉树中插入与删除结点 </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-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-content">
|
||||||
<div class="tabbed-block">
|
<div class="tabbed-block">
|
||||||
|
@ -2044,16 +2050,24 @@
|
||||||
<p>在中文社区中,完美二叉树常被称为「满二叉树」,请注意与完满二叉树区分。</p>
|
<p>在中文社区中,完美二叉树常被称为「满二叉树」,请注意与完满二叉树区分。</p>
|
||||||
</div>
|
</div>
|
||||||
<p><img alt="完美二叉树" src="../binary_tree.assets/perfect_binary_tree.png" /></p>
|
<p><img alt="完美二叉树" src="../binary_tree.assets/perfect_binary_tree.png" /></p>
|
||||||
|
<p align="center"> Fig. 完美二叉树 </p>
|
||||||
|
|
||||||
<h3 id="_2">完全二叉树<a class="headerlink" href="#_2" title="Permanent link">¶</a></h3>
|
<h3 id="_2">完全二叉树<a class="headerlink" href="#_2" title="Permanent link">¶</a></h3>
|
||||||
<p>「完全二叉树 Complete Binary Tree」只有最底层的结点未被填满,且最底层结点尽量靠左填充。</p>
|
<p>「完全二叉树 Complete Binary Tree」只有最底层的结点未被填满,且最底层结点尽量靠左填充。</p>
|
||||||
<p><strong>完全二叉树非常适合用数组来表示</strong>。如果按照层序遍历序列的顺序来存储,那么空结点 <code>null</code> 一定全部出现在序列的尾部,因此我们就可以不用存储这些 null 了。</p>
|
<p><strong>完全二叉树非常适合用数组来表示</strong>。如果按照层序遍历序列的顺序来存储,那么空结点 <code>null</code> 一定全部出现在序列的尾部,因此我们就可以不用存储这些 null 了。</p>
|
||||||
<p><img alt="完全二叉树" src="../binary_tree.assets/complete_binary_tree.png" /></p>
|
<p><img alt="完全二叉树" src="../binary_tree.assets/complete_binary_tree.png" /></p>
|
||||||
|
<p align="center"> Fig. 完全二叉树 </p>
|
||||||
|
|
||||||
<h3 id="_3">完满二叉树<a class="headerlink" href="#_3" title="Permanent link">¶</a></h3>
|
<h3 id="_3">完满二叉树<a class="headerlink" href="#_3" title="Permanent link">¶</a></h3>
|
||||||
<p>「完满二叉树 Full Binary Tree」除了叶结点之外,其余所有结点都有两个子结点。</p>
|
<p>「完满二叉树 Full Binary Tree」除了叶结点之外,其余所有结点都有两个子结点。</p>
|
||||||
<p><img alt="完满二叉树" src="../binary_tree.assets/full_binary_tree.png" /></p>
|
<p><img alt="完满二叉树" src="../binary_tree.assets/full_binary_tree.png" /></p>
|
||||||
|
<p align="center"> Fig. 完满二叉树 </p>
|
||||||
|
|
||||||
<h3 id="_4">平衡二叉树<a class="headerlink" href="#_4" title="Permanent link">¶</a></h3>
|
<h3 id="_4">平衡二叉树<a class="headerlink" href="#_4" title="Permanent link">¶</a></h3>
|
||||||
<p>「平衡二叉树 Balanced Binary Tree」中任意结点的左子树和右子树的高度之差的绝对值 <span class="arithmatex">\(\leq 1\)</span> 。</p>
|
<p>「平衡二叉树 Balanced Binary Tree」中任意结点的左子树和右子树的高度之差的绝对值 <span class="arithmatex">\(\leq 1\)</span> 。</p>
|
||||||
<p><img alt="平衡二叉树" src="../binary_tree.assets/balanced_binary_tree.png" /></p>
|
<p><img alt="平衡二叉树" src="../binary_tree.assets/balanced_binary_tree.png" /></p>
|
||||||
|
<p align="center"> Fig. 平衡二叉树 </p>
|
||||||
|
|
||||||
<h2 id="714">7.1.4. 二叉树的退化<a class="headerlink" href="#714" title="Permanent link">¶</a></h2>
|
<h2 id="714">7.1.4. 二叉树的退化<a class="headerlink" href="#714" title="Permanent link">¶</a></h2>
|
||||||
<p>当二叉树的每层的结点都被填满时,达到「完美二叉树」;而当所有结点都偏向一边时,二叉树退化为「链表」。</p>
|
<p>当二叉树的每层的结点都被填满时,达到「完美二叉树」;而当所有结点都偏向一边时,二叉树退化为「链表」。</p>
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -2061,6 +2075,8 @@
|
||||||
<li>链表则是另一个极端,各项操作都变为线性操作,时间复杂度退化至 <span class="arithmatex">\(O(n)\)</span> ;</li>
|
<li>链表则是另一个极端,各项操作都变为线性操作,时间复杂度退化至 <span class="arithmatex">\(O(n)\)</span> ;</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p><img alt="二叉树的最佳与最二叉树的最佳和最差结构差情况" src="../binary_tree.assets/binary_tree_corner_cases.png" /></p>
|
<p><img alt="二叉树的最佳与最二叉树的最佳和最差结构差情况" src="../binary_tree.assets/binary_tree_corner_cases.png" /></p>
|
||||||
|
<p align="center"> Fig. 二叉树的最佳与最二叉树的最佳和最差结构差情况 </p>
|
||||||
|
|
||||||
<p>如下表所示,在最佳和最差结构下,二叉树的叶结点数量、结点总数、高度等达到极大或极小值。</p>
|
<p>如下表所示,在最佳和最差结构下,二叉树的叶结点数量、结点总数、高度等达到极大或极小值。</p>
|
||||||
<div class="center-table">
|
<div class="center-table">
|
||||||
<table>
|
<table>
|
||||||
|
@ -2100,8 +2116,12 @@
|
||||||
<p>那能否可以用「数组表示」二叉树呢?答案是肯定的。先来分析一个简单案例,给定一个「完美二叉树」,将结点按照层序遍历的顺序编号(从 0 开始),那么可以推导得出父结点索引与子结点索引之间的「映射公式」:<strong>设结点的索引为 <span class="arithmatex">\(i\)</span> ,则该结点的左子结点索引为 <span class="arithmatex">\(2i + 1\)</span> 、右子结点索引为 <span class="arithmatex">\(2i + 2\)</span></strong> 。</p>
|
<p>那能否可以用「数组表示」二叉树呢?答案是肯定的。先来分析一个简单案例,给定一个「完美二叉树」,将结点按照层序遍历的顺序编号(从 0 开始),那么可以推导得出父结点索引与子结点索引之间的「映射公式」:<strong>设结点的索引为 <span class="arithmatex">\(i\)</span> ,则该结点的左子结点索引为 <span class="arithmatex">\(2i + 1\)</span> 、右子结点索引为 <span class="arithmatex">\(2i + 2\)</span></strong> 。</p>
|
||||||
<p><strong>本质上,映射公式的作用就是链表中的指针</strong>。对于层序遍历序列中的任意结点,我们都可以使用映射公式来访问子结点。因此,可以直接使用层序遍历序列(即数组)来表示完美二叉树。</p>
|
<p><strong>本质上,映射公式的作用就是链表中的指针</strong>。对于层序遍历序列中的任意结点,我们都可以使用映射公式来访问子结点。因此,可以直接使用层序遍历序列(即数组)来表示完美二叉树。</p>
|
||||||
<p><img alt="完美二叉树的数组表示" src="../binary_tree.assets/array_representation_mapping.png" /></p>
|
<p><img alt="完美二叉树的数组表示" src="../binary_tree.assets/array_representation_mapping.png" /></p>
|
||||||
|
<p align="center"> Fig. 完美二叉树的数组表示 </p>
|
||||||
|
|
||||||
<p>然而,完美二叉树只是个例,二叉树中间层往往存在许多空结点(即 <code>null</code> ),而层序遍历序列并不包含这些空结点,并且我们无法单凭序列来猜测空结点的数量和分布位置,<strong>即理论上存在许多种二叉树都符合该层序遍历序列</strong>。显然,这种情况无法使用数组来存储二叉树。</p>
|
<p>然而,完美二叉树只是个例,二叉树中间层往往存在许多空结点(即 <code>null</code> ),而层序遍历序列并不包含这些空结点,并且我们无法单凭序列来猜测空结点的数量和分布位置,<strong>即理论上存在许多种二叉树都符合该层序遍历序列</strong>。显然,这种情况无法使用数组来存储二叉树。</p>
|
||||||
<p><img alt="给定数组对应多种二叉树可能性" src="../binary_tree.assets/array_representation_without_empty.png" /></p>
|
<p><img alt="给定数组对应多种二叉树可能性" src="../binary_tree.assets/array_representation_without_empty.png" /></p>
|
||||||
|
<p align="center"> Fig. 给定数组对应多种二叉树可能性 </p>
|
||||||
|
|
||||||
<p>为了解决此问题,考虑按照完美二叉树的形式来表示所有二叉树,<strong>即在序列中使用特殊符号来显式地表示“空位”</strong>。如下图所示,这样处理后,序列(数组)就可以唯一表示二叉树了。</p>
|
<p>为了解决此问题,考虑按照完美二叉树的形式来表示所有二叉树,<strong>即在序列中使用特殊符号来显式地表示“空位”</strong>。如下图所示,这样处理后,序列(数组)就可以唯一表示二叉树了。</p>
|
||||||
<div class="tabbed-set tabbed-alternate" data-tabs="4:10"><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" /><input id="__tabbed_4_5" name="__tabbed_4" type="radio" /><input id="__tabbed_4_6" name="__tabbed_4" type="radio" /><input id="__tabbed_4_7" name="__tabbed_4" type="radio" /><input id="__tabbed_4_8" name="__tabbed_4" type="radio" /><input id="__tabbed_4_9" name="__tabbed_4" type="radio" /><input id="__tabbed_4_10" name="__tabbed_4" type="radio" /><div class="tabbed-labels"><label for="__tabbed_4_1">Java</label><label for="__tabbed_4_2">C++</label><label for="__tabbed_4_3">Python</label><label for="__tabbed_4_4">Go</label><label for="__tabbed_4_5">JavaScript</label><label for="__tabbed_4_6">TypeScript</label><label for="__tabbed_4_7">C</label><label for="__tabbed_4_8">C#</label><label for="__tabbed_4_9">Swift</label><label for="__tabbed_4_10">Zig</label></div>
|
<div class="tabbed-set tabbed-alternate" data-tabs="4:10"><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" /><input id="__tabbed_4_5" name="__tabbed_4" type="radio" /><input id="__tabbed_4_6" name="__tabbed_4" type="radio" /><input id="__tabbed_4_7" name="__tabbed_4" type="radio" /><input id="__tabbed_4_8" name="__tabbed_4" type="radio" /><input id="__tabbed_4_9" name="__tabbed_4" type="radio" /><input id="__tabbed_4_10" name="__tabbed_4" type="radio" /><div class="tabbed-labels"><label for="__tabbed_4_1">Java</label><label for="__tabbed_4_2">C++</label><label for="__tabbed_4_3">Python</label><label for="__tabbed_4_4">Go</label><label for="__tabbed_4_5">JavaScript</label><label for="__tabbed_4_6">TypeScript</label><label for="__tabbed_4_7">C</label><label for="__tabbed_4_8">C#</label><label for="__tabbed_4_9">Swift</label><label for="__tabbed_4_10">Zig</label></div>
|
||||||
<div class="tabbed-content">
|
<div class="tabbed-content">
|
||||||
|
@ -2163,8 +2183,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p><img alt="任意类型二叉树的数组表示" src="../binary_tree.assets/array_representation_with_empty.png" /></p>
|
<p><img alt="任意类型二叉树的数组表示" src="../binary_tree.assets/array_representation_with_empty.png" /></p>
|
||||||
|
<p align="center"> Fig. 任意类型二叉树的数组表示 </p>
|
||||||
|
|
||||||
<p>回顾「完全二叉树」的定义,其只有最底层有空结点,并且最底层的结点尽量靠左,因而所有空结点都一定出现在层序遍历序列的末尾。<strong>因为我们先验地确定了空位的位置,所以在使用数组表示完全二叉树时,可以省略存储“空位”</strong>。因此,完全二叉树非常适合使用数组来表示。</p>
|
<p>回顾「完全二叉树」的定义,其只有最底层有空结点,并且最底层的结点尽量靠左,因而所有空结点都一定出现在层序遍历序列的末尾。<strong>因为我们先验地确定了空位的位置,所以在使用数组表示完全二叉树时,可以省略存储“空位”</strong>。因此,完全二叉树非常适合使用数组来表示。</p>
|
||||||
<p><img alt="完全二叉树的数组表示" src="../binary_tree.assets/array_representation_complete_binary_tree.png" /></p>
|
<p><img alt="完全二叉树的数组表示" src="../binary_tree.assets/array_representation_complete_binary_tree.png" /></p>
|
||||||
|
<p align="center"> Fig. 完全二叉树的数组表示 </p>
|
||||||
|
|
||||||
<p>数组表示有两个优点: 一是不需要存储指针,节省空间;二是可以随机访问结点。然而,当二叉树中的“空位”很多时,数组中只包含很少结点的数据,空间利用率很低。</p>
|
<p>数组表示有两个优点: 一是不需要存储指针,节省空间;二是可以随机访问结点。然而,当二叉树中的“空位”很多时,数组中只包含很少结点的数据,空间利用率很低。</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1658,6 +1658,8 @@
|
||||||
<p>「层序遍历 Level-Order Traversal」从顶至底、一层一层地遍历二叉树,并在每层中按照从左到右的顺序访问结点。</p>
|
<p>「层序遍历 Level-Order Traversal」从顶至底、一层一层地遍历二叉树,并在每层中按照从左到右的顺序访问结点。</p>
|
||||||
<p>层序遍历本质上是「广度优先搜索 Breadth-First Traversal」,其体现着一种“一圈一圈向外”的层进遍历方式。</p>
|
<p>层序遍历本质上是「广度优先搜索 Breadth-First Traversal」,其体现着一种“一圈一圈向外”的层进遍历方式。</p>
|
||||||
<p><img alt="二叉树的层序遍历" src="../binary_tree_traversal.assets/binary_tree_bfs.png" /></p>
|
<p><img alt="二叉树的层序遍历" src="../binary_tree_traversal.assets/binary_tree_bfs.png" /></p>
|
||||||
|
<p align="center"> Fig. 二叉树的层序遍历 </p>
|
||||||
|
|
||||||
<h3 id="_1">算法实现<a class="headerlink" href="#_1" title="Permanent link">¶</a></h3>
|
<h3 id="_1">算法实现<a class="headerlink" href="#_1" title="Permanent link">¶</a></h3>
|
||||||
<p>广度优先遍历一般借助「队列」来实现。队列的规则是“先进先出”,广度优先遍历的规则是 ”一层层平推“ ,两者背后的思想是一致的。</p>
|
<p>广度优先遍历一般借助「队列」来实现。队列的规则是“先进先出”,广度优先遍历的规则是 ”一层层平推“ ,两者背后的思想是一致的。</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-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>
|
||||||
|
@ -1873,6 +1875,8 @@
|
||||||
<p>相对地,前、中、后序遍历皆属于「深度优先遍历 Depth-First Traversal」,其体现着一种“先走到尽头,再回头继续”的回溯遍历方式。</p>
|
<p>相对地,前、中、后序遍历皆属于「深度优先遍历 Depth-First Traversal」,其体现着一种“先走到尽头,再回头继续”的回溯遍历方式。</p>
|
||||||
<p>如下图所示,左侧是深度优先遍历的的示意图,右上方是对应的递归实现代码。深度优先遍历就像是绕着整个二叉树的外围“走”一圈,走的过程中,在每个结点都会遇到三个位置,分别对应前序遍历、中序遍历、后序遍历。</p>
|
<p>如下图所示,左侧是深度优先遍历的的示意图,右上方是对应的递归实现代码。深度优先遍历就像是绕着整个二叉树的外围“走”一圈,走的过程中,在每个结点都会遇到三个位置,分别对应前序遍历、中序遍历、后序遍历。</p>
|
||||||
<p><img alt="二叉搜索树的前、中、后序遍历" src="../binary_tree_traversal.assets/binary_tree_dfs.png" /></p>
|
<p><img alt="二叉搜索树的前、中、后序遍历" src="../binary_tree_traversal.assets/binary_tree_dfs.png" /></p>
|
||||||
|
<p align="center"> Fig. 二叉搜索树的前、中、后序遍历 </p>
|
||||||
|
|
||||||
<div class="center-table">
|
<div class="center-table">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
|
|
File diff suppressed because one or more lines are too long
BIN
sitemap.xml.gz
BIN
sitemap.xml.gz
Binary file not shown.
Loading…
Reference in a new issue