This commit is contained in:
krahets
2023-04-10 03:12:10 +08:00
parent dda68e47c1
commit 9393f5957c
35 changed files with 399 additions and 544 deletions

View File

@ -1722,7 +1722,7 @@
</div>
<div class="admonition note">
<p class="admonition-title">缓存局部性</p>
<p>在计算机中,数据读写速度排序是“硬盘 &lt; 内存 &lt; CPU 缓存”。当我们访问数组元素时,计算机不仅会加载它,还会缓存其周围的其数据,从而借助高速缓存来提升后续操作的执行速度。链表则不然,计算机只能挨个地缓存各个节点,这样的多次“搬运”降低了整体效率。</p>
<p>在计算机中,数据读写速度排序是“硬盘 &lt; 内存 &lt; CPU 缓存”。当我们访问数组元素时,计算机不仅会加载它,还会缓存其周围的其数据,从而借助高速缓存来提升后续操作的执行速度。链表则不然,计算机只能挨个地缓存各个节点,这样的多次“搬运”降低了整体效率。</p>
</div>
<ul>
<li>下表对比了数组与链表在各种操作上的效率。</li>

View File

@ -2085,7 +2085,7 @@
<p>然而实际上,<strong>统计算法的运行时间既不合理也不现实</strong>。首先,我们不希望预估时间和运行平台绑定,因为算法需要在各种不同的平台上运行。其次,我们很难获知每种操作的运行时间,这给预估过程带来了极大的难度。</p>
<h2 id="222">2.2.2. &nbsp; 统计时间增长趋势<a class="headerlink" href="#222" title="Permanent link">&para;</a></h2>
<p>「时间复杂度分析」采取了一种不同的方法,其统计的不是算法运行时间,<strong>而是算法运行时间随着数据量变大时的增长趋势</strong></p>
<p>“时间增长趋势”这个概念较为抽象,我们通过一个例子来加以理解。假设输入数据大小为 <span class="arithmatex">\(n\)</span>,给定三个算法 <code>A</code><code>B</code><code>C</code></p>
<p>“时间增长趋势”这个概念较为抽象,我们通过一个例子来加以理解。假设输入数据大小为 <span class="arithmatex">\(n\)</span> ,给定三个算法 <code>A</code> , <code>B</code> , <code>C</code></p>
<ul>
<li>算法 <code>A</code> 只有 <span class="arithmatex">\(1\)</span> 个打印操作,算法运行时间不随着 <span class="arithmatex">\(n\)</span> 增大而增长。我们称此算法的时间复杂度为「常数阶」。</li>
<li>算法 <code>B</code> 中的打印操作需要循环 <span class="arithmatex">\(n\)</span> 次,算法运行时间随着 <span class="arithmatex">\(n\)</span> 增大呈线性增长。此算法的时间复杂度被称为「线性阶」。</li>
@ -2599,7 +2599,7 @@ T(n) &amp; = n^2 + n &amp; \text{偷懒统计 (o.O)}
</div>
</div>
<h3 id="2">2) 判断渐近上界<a class="headerlink" href="#2" title="Permanent link">&para;</a></h3>
<p><strong>时间复杂度由多项式 <span class="arithmatex">\(T(n)\)</span> 中最高阶的项来决定</strong>。这是因为在 <span class="arithmatex">\(n\)</span> 趋于无穷大时,最高阶的项将发挥主导作用,其项的影响都可以被忽略。</p>
<p><strong>时间复杂度由多项式 <span class="arithmatex">\(T(n)\)</span> 中最高阶的项来决定</strong>。这是因为在 <span class="arithmatex">\(n\)</span> 趋于无穷大时,最高阶的项将发挥主导作用,其项的影响都可以被忽略。</p>
<p>以下表格展示了一些例子,其中一些夸张的值是为了强调“系数无法撼动阶数”这一结论。当 <span class="arithmatex">\(n\)</span> 趋于无穷大时,这些常数变得无足轻重。</p>
<div class="center-table">
<table>

View File

@ -1883,7 +1883,7 @@
<li>当所有 bits 为 0 时代表数字 <span class="arithmatex">\(0\)</span> ,从零开始增大,可得最大正数为 <span class="arithmatex">\(2^{31} - 1\)</span> </li>
<li>剩余 <span class="arithmatex">\(2^{31}\)</span> 个数字全部用来表示负数,因此最小负数为 <span class="arithmatex">\(-2^{31}\)</span> ;具体细节涉及“源码、反码、补码”的相关知识,有兴趣的同学可以查阅学习;</li>
</ol>
<p>整数类型 byte, short, long 的取值范围的计算方法与 int 类似,在此不再赘述。</p>
<p>整数类型 byte, short, long 的取值范围的计算方法与 int 类似,在此不再赘述。</p>
<h3 id="_2">浮点数表示方式 *<a class="headerlink" href="#_2" title="Permanent link">&para;</a></h3>
<div class="admonition note">
<p class="admonition-title">Note</p>

View File

@ -2600,7 +2600,7 @@
<a id="__codelineno-10-51" name="__codelineno-10-51" href="#__codelineno-10-51"></a><span class="w"> </span><span class="k">throw</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">IllegalArgumentException</span><span class="p">();</span>
<a id="__codelineno-10-52" name="__codelineno-10-52" href="#__codelineno-10-52"></a><span class="w"> </span><span class="c1">// 在邻接表中删除顶点 vet 对应的链表</span>
<a id="__codelineno-10-53" name="__codelineno-10-53" href="#__codelineno-10-53"></a><span class="w"> </span><span class="n">adjList</span><span class="p">.</span><span class="na">remove</span><span class="p">(</span><span class="n">vet</span><span class="p">);</span>
<a id="__codelineno-10-54" name="__codelineno-10-54" href="#__codelineno-10-54"></a><span class="w"> </span><span class="c1">// 遍历其顶点的链表,删除所有包含 vet 的边</span>
<a id="__codelineno-10-54" name="__codelineno-10-54" href="#__codelineno-10-54"></a><span class="w"> </span><span class="c1">// 遍历其顶点的链表,删除所有包含 vet 的边</span>
<a id="__codelineno-10-55" name="__codelineno-10-55" href="#__codelineno-10-55"></a><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="n">List</span><span class="o">&lt;</span><span class="n">Vertex</span><span class="o">&gt;</span><span class="w"> </span><span class="n">list</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="n">adjList</span><span class="p">.</span><span class="na">values</span><span class="p">())</span><span class="w"> </span><span class="p">{</span>
<a id="__codelineno-10-56" name="__codelineno-10-56" href="#__codelineno-10-56"></a><span class="w"> </span><span class="n">list</span><span class="p">.</span><span class="na">remove</span><span class="p">(</span><span class="n">vet</span><span class="p">);</span>
<a id="__codelineno-10-57" name="__codelineno-10-57" href="#__codelineno-10-57"></a><span class="w"> </span><span class="p">}</span>
@ -2620,83 +2620,7 @@
</code></pre></div>
</div>
<div class="tabbed-block">
<div class="highlight"><span class="filename">graph_adjacency_list.cpp</span><pre><span></span><code><a id="__codelineno-11-1" name="__codelineno-11-1" href="#__codelineno-11-1"></a><span class="cm">/* 基于邻接表实现的无向图类 */</span>
<a id="__codelineno-11-2" name="__codelineno-11-2" href="#__codelineno-11-2"></a><span class="k">class</span><span class="w"> </span><span class="nc">GraphAdjList</span><span class="w"> </span><span class="p">{</span>
<a id="__codelineno-11-3" name="__codelineno-11-3" href="#__codelineno-11-3"></a><span class="k">public</span><span class="o">:</span>
<a id="__codelineno-11-4" name="__codelineno-11-4" href="#__codelineno-11-4"></a><span class="w"> </span><span class="c1">// 邻接表key: 顶点value该顶点的所有邻接顶点</span>
<a id="__codelineno-11-5" name="__codelineno-11-5" href="#__codelineno-11-5"></a><span class="w"> </span><span class="n">unordered_map</span><span class="o">&lt;</span><span class="n">Vertex</span><span class="o">*</span><span class="p">,</span><span class="w"> </span><span class="n">vector</span><span class="o">&lt;</span><span class="n">Vertex</span><span class="o">*&gt;&gt;</span><span class="w"> </span><span class="n">adjList</span><span class="p">;</span>
<a id="__codelineno-11-6" name="__codelineno-11-6" href="#__codelineno-11-6"></a>
<a id="__codelineno-11-7" name="__codelineno-11-7" href="#__codelineno-11-7"></a><span class="w"> </span><span class="cm">/* 在 vector 中删除指定节点 */</span>
<a id="__codelineno-11-8" name="__codelineno-11-8" href="#__codelineno-11-8"></a><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">remove</span><span class="p">(</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">Vertex</span><span class="o">*&gt;</span><span class="w"> </span><span class="o">&amp;</span><span class="n">vec</span><span class="p">,</span><span class="w"> </span><span class="n">Vertex</span><span class="w"> </span><span class="o">*</span><span class="n">vet</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<a id="__codelineno-11-9" name="__codelineno-11-9" href="#__codelineno-11-9"></a><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="kt">int</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="n">vec</span><span class="p">.</span><span class="n">size</span><span class="p">();</span><span class="w"> </span><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<a id="__codelineno-11-10" name="__codelineno-11-10" href="#__codelineno-11-10"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">vec</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">vet</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<a id="__codelineno-11-11" name="__codelineno-11-11" href="#__codelineno-11-11"></a><span class="w"> </span><span class="n">vec</span><span class="p">.</span><span class="n">erase</span><span class="p">(</span><span class="n">vec</span><span class="p">.</span><span class="n">begin</span><span class="p">()</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">i</span><span class="p">);</span>
<a id="__codelineno-11-12" name="__codelineno-11-12" href="#__codelineno-11-12"></a><span class="w"> </span><span class="k">break</span><span class="p">;</span>
<a id="__codelineno-11-13" name="__codelineno-11-13" href="#__codelineno-11-13"></a><span class="w"> </span><span class="p">}</span>
<a id="__codelineno-11-14" name="__codelineno-11-14" href="#__codelineno-11-14"></a><span class="w"> </span><span class="p">}</span>
<a id="__codelineno-11-15" name="__codelineno-11-15" href="#__codelineno-11-15"></a><span class="w"> </span><span class="p">}</span>
<a id="__codelineno-11-16" name="__codelineno-11-16" href="#__codelineno-11-16"></a>
<a id="__codelineno-11-17" name="__codelineno-11-17" href="#__codelineno-11-17"></a><span class="w"> </span><span class="cm">/* 构造方法 */</span>
<a id="__codelineno-11-18" name="__codelineno-11-18" href="#__codelineno-11-18"></a><span class="w"> </span><span class="n">GraphAdjList</span><span class="p">(</span><span class="k">const</span><span class="w"> </span><span class="n">vector</span><span class="o">&lt;</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">Vertex</span><span class="o">*&gt;&gt;&amp;</span><span class="w"> </span><span class="n">edges</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<a id="__codelineno-11-19" name="__codelineno-11-19" href="#__codelineno-11-19"></a><span class="w"> </span><span class="c1">// 添加所有顶点和边</span>
<a id="__codelineno-11-20" name="__codelineno-11-20" href="#__codelineno-11-20"></a><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="k">const</span><span class="w"> </span><span class="n">vector</span><span class="o">&lt;</span><span class="n">Vertex</span><span class="o">*&gt;&amp;</span><span class="w"> </span><span class="n">edge</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="n">edges</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<a id="__codelineno-11-21" name="__codelineno-11-21" href="#__codelineno-11-21"></a><span class="w"> </span><span class="n">addVertex</span><span class="p">(</span><span class="n">edge</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
<a id="__codelineno-11-22" name="__codelineno-11-22" href="#__codelineno-11-22"></a><span class="w"> </span><span class="n">addVertex</span><span class="p">(</span><span class="n">edge</span><span class="p">[</span><span class="mi">1</span><span class="p">]);</span>
<a id="__codelineno-11-23" name="__codelineno-11-23" href="#__codelineno-11-23"></a><span class="w"> </span><span class="n">addEdge</span><span class="p">(</span><span class="n">edge</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span><span class="w"> </span><span class="n">edge</span><span class="p">[</span><span class="mi">1</span><span class="p">]);</span>
<a id="__codelineno-11-24" name="__codelineno-11-24" href="#__codelineno-11-24"></a><span class="w"> </span><span class="p">}</span>
<a id="__codelineno-11-25" name="__codelineno-11-25" href="#__codelineno-11-25"></a><span class="w"> </span><span class="p">}</span>
<a id="__codelineno-11-26" name="__codelineno-11-26" href="#__codelineno-11-26"></a>
<a id="__codelineno-11-27" name="__codelineno-11-27" href="#__codelineno-11-27"></a><span class="w"> </span><span class="cm">/* 获取顶点数量 */</span>
<a id="__codelineno-11-28" name="__codelineno-11-28" href="#__codelineno-11-28"></a><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">size</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">adjList</span><span class="p">.</span><span class="n">size</span><span class="p">();</span><span class="w"> </span><span class="p">}</span>
<a id="__codelineno-11-29" name="__codelineno-11-29" href="#__codelineno-11-29"></a>
<a id="__codelineno-11-30" name="__codelineno-11-30" href="#__codelineno-11-30"></a><span class="w"> </span><span class="cm">/* 添加边 */</span>
<a id="__codelineno-11-31" name="__codelineno-11-31" href="#__codelineno-11-31"></a><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="n">addEdge</span><span class="p">(</span><span class="n">Vertex</span><span class="o">*</span><span class="w"> </span><span class="n">vet1</span><span class="p">,</span><span class="w"> </span><span class="n">Vertex</span><span class="o">*</span><span class="w"> </span><span class="n">vet2</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<a id="__codelineno-11-32" name="__codelineno-11-32" href="#__codelineno-11-32"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="n">adjList</span><span class="p">.</span><span class="n">count</span><span class="p">(</span><span class="n">vet1</span><span class="p">)</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="o">!</span><span class="n">adjList</span><span class="p">.</span><span class="n">count</span><span class="p">(</span><span class="n">vet2</span><span class="p">)</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="n">vet1</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">vet2</span><span class="p">)</span>
<a id="__codelineno-11-33" name="__codelineno-11-33" href="#__codelineno-11-33"></a><span class="w"> </span><span class="k">throw</span><span class="w"> </span><span class="n">invalid_argument</span><span class="p">(</span><span class="s">&quot;不存在顶点&quot;</span><span class="p">);</span>
<a id="__codelineno-11-34" name="__codelineno-11-34" href="#__codelineno-11-34"></a><span class="w"> </span><span class="c1">// 添加边 vet1 - vet2</span>
<a id="__codelineno-11-35" name="__codelineno-11-35" href="#__codelineno-11-35"></a><span class="w"> </span><span class="n">adjList</span><span class="p">[</span><span class="n">vet1</span><span class="p">].</span><span class="n">push_back</span><span class="p">(</span><span class="n">vet2</span><span class="p">);</span>
<a id="__codelineno-11-36" name="__codelineno-11-36" href="#__codelineno-11-36"></a><span class="w"> </span><span class="n">adjList</span><span class="p">[</span><span class="n">vet2</span><span class="p">].</span><span class="n">push_back</span><span class="p">(</span><span class="n">vet1</span><span class="p">);</span>
<a id="__codelineno-11-37" name="__codelineno-11-37" href="#__codelineno-11-37"></a><span class="w"> </span><span class="p">}</span>
<a id="__codelineno-11-38" name="__codelineno-11-38" href="#__codelineno-11-38"></a>
<a id="__codelineno-11-39" name="__codelineno-11-39" href="#__codelineno-11-39"></a><span class="w"> </span><span class="cm">/* 删除边 */</span>
<a id="__codelineno-11-40" name="__codelineno-11-40" href="#__codelineno-11-40"></a><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="n">removeEdge</span><span class="p">(</span><span class="n">Vertex</span><span class="o">*</span><span class="w"> </span><span class="n">vet1</span><span class="p">,</span><span class="w"> </span><span class="n">Vertex</span><span class="o">*</span><span class="w"> </span><span class="n">vet2</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<a id="__codelineno-11-41" name="__codelineno-11-41" href="#__codelineno-11-41"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="n">adjList</span><span class="p">.</span><span class="n">count</span><span class="p">(</span><span class="n">vet1</span><span class="p">)</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="o">!</span><span class="n">adjList</span><span class="p">.</span><span class="n">count</span><span class="p">(</span><span class="n">vet2</span><span class="p">)</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="n">vet1</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">vet2</span><span class="p">)</span>
<a id="__codelineno-11-42" name="__codelineno-11-42" href="#__codelineno-11-42"></a><span class="w"> </span><span class="k">throw</span><span class="w"> </span><span class="n">invalid_argument</span><span class="p">(</span><span class="s">&quot;不存在顶点&quot;</span><span class="p">);</span>
<a id="__codelineno-11-43" name="__codelineno-11-43" href="#__codelineno-11-43"></a><span class="w"> </span><span class="c1">// 删除边 vet1 - vet2</span>
<a id="__codelineno-11-44" name="__codelineno-11-44" href="#__codelineno-11-44"></a><span class="w"> </span><span class="n">remove</span><span class="p">(</span><span class="n">adjList</span><span class="p">[</span><span class="n">vet1</span><span class="p">],</span><span class="w"> </span><span class="n">vet2</span><span class="p">);</span>
<a id="__codelineno-11-45" name="__codelineno-11-45" href="#__codelineno-11-45"></a><span class="w"> </span><span class="n">remove</span><span class="p">(</span><span class="n">adjList</span><span class="p">[</span><span class="n">vet2</span><span class="p">],</span><span class="w"> </span><span class="n">vet1</span><span class="p">);</span>
<a id="__codelineno-11-46" name="__codelineno-11-46" href="#__codelineno-11-46"></a><span class="w"> </span><span class="p">}</span>
<a id="__codelineno-11-47" name="__codelineno-11-47" href="#__codelineno-11-47"></a>
<a id="__codelineno-11-48" name="__codelineno-11-48" href="#__codelineno-11-48"></a><span class="w"> </span><span class="cm">/* 添加顶点 */</span>
<a id="__codelineno-11-49" name="__codelineno-11-49" href="#__codelineno-11-49"></a><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="n">addVertex</span><span class="p">(</span><span class="n">Vertex</span><span class="o">*</span><span class="w"> </span><span class="n">vet</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<a id="__codelineno-11-50" name="__codelineno-11-50" href="#__codelineno-11-50"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">adjList</span><span class="p">.</span><span class="n">count</span><span class="p">(</span><span class="n">vet</span><span class="p">))</span><span class="w"> </span><span class="k">return</span><span class="p">;</span>
<a id="__codelineno-11-51" name="__codelineno-11-51" href="#__codelineno-11-51"></a><span class="w"> </span><span class="c1">// 在邻接表中添加一个新链表</span>
<a id="__codelineno-11-52" name="__codelineno-11-52" href="#__codelineno-11-52"></a><span class="w"> </span><span class="n">adjList</span><span class="p">[</span><span class="n">vet</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">vector</span><span class="o">&lt;</span><span class="n">Vertex</span><span class="o">*&gt;</span><span class="p">();</span>
<a id="__codelineno-11-53" name="__codelineno-11-53" href="#__codelineno-11-53"></a><span class="w"> </span><span class="p">}</span>
<a id="__codelineno-11-54" name="__codelineno-11-54" href="#__codelineno-11-54"></a>
<a id="__codelineno-11-55" name="__codelineno-11-55" href="#__codelineno-11-55"></a><span class="w"> </span><span class="cm">/* 删除顶点 */</span>
<a id="__codelineno-11-56" name="__codelineno-11-56" href="#__codelineno-11-56"></a><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="n">removeVertex</span><span class="p">(</span><span class="n">Vertex</span><span class="o">*</span><span class="w"> </span><span class="n">vet</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<a id="__codelineno-11-57" name="__codelineno-11-57" href="#__codelineno-11-57"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="n">adjList</span><span class="p">.</span><span class="n">count</span><span class="p">(</span><span class="n">vet</span><span class="p">))</span>
<a id="__codelineno-11-58" name="__codelineno-11-58" href="#__codelineno-11-58"></a><span class="w"> </span><span class="k">throw</span><span class="w"> </span><span class="n">invalid_argument</span><span class="p">(</span><span class="s">&quot;不存在顶点&quot;</span><span class="p">);</span>
<a id="__codelineno-11-59" name="__codelineno-11-59" href="#__codelineno-11-59"></a><span class="w"> </span><span class="c1">// 在邻接表中删除顶点 vet 对应的链表</span>
<a id="__codelineno-11-60" name="__codelineno-11-60" href="#__codelineno-11-60"></a><span class="w"> </span><span class="n">adjList</span><span class="p">.</span><span class="n">erase</span><span class="p">(</span><span class="n">vet</span><span class="p">);</span>
<a id="__codelineno-11-61" name="__codelineno-11-61" href="#__codelineno-11-61"></a><span class="w"> </span><span class="c1">// 遍历其它顶点的链表,删除所有包含 vet 的边</span>
<a id="__codelineno-11-62" name="__codelineno-11-62" href="#__codelineno-11-62"></a><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="k">auto</span><span class="o">&amp;</span><span class="w"> </span><span class="p">[</span><span class="n">key</span><span class="p">,</span><span class="w"> </span><span class="n">vec</span><span class="p">]</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="n">adjList</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<a id="__codelineno-11-63" name="__codelineno-11-63" href="#__codelineno-11-63"></a><span class="w"> </span><span class="n">remove</span><span class="p">(</span><span class="n">vec</span><span class="p">,</span><span class="w"> </span><span class="n">vet</span><span class="p">);</span>
<a id="__codelineno-11-64" name="__codelineno-11-64" href="#__codelineno-11-64"></a><span class="w"> </span><span class="p">}</span>
<a id="__codelineno-11-65" name="__codelineno-11-65" href="#__codelineno-11-65"></a><span class="w"> </span><span class="p">}</span>
<a id="__codelineno-11-66" name="__codelineno-11-66" href="#__codelineno-11-66"></a>
<a id="__codelineno-11-67" name="__codelineno-11-67" href="#__codelineno-11-67"></a><span class="w"> </span><span class="cm">/* 打印邻接表 */</span>
<a id="__codelineno-11-68" name="__codelineno-11-68" href="#__codelineno-11-68"></a><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="n">print</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<a id="__codelineno-11-69" name="__codelineno-11-69" href="#__codelineno-11-69"></a><span class="w"> </span><span class="n">cout</span><span class="w"> </span><span class="o">&lt;&lt;</span><span class="w"> </span><span class="s">&quot;邻接表 =&quot;</span><span class="w"> </span><span class="o">&lt;&lt;</span><span class="w"> </span><span class="n">endl</span><span class="p">;</span>
<a id="__codelineno-11-70" name="__codelineno-11-70" href="#__codelineno-11-70"></a><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="k">auto</span><span class="o">&amp;</span><span class="w"> </span><span class="n">adj</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="n">adjList</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<a id="__codelineno-11-71" name="__codelineno-11-71" href="#__codelineno-11-71"></a><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="k">auto</span><span class="o">&amp;</span><span class="w"> </span><span class="n">key</span><span class="o">=</span><span class="w"> </span><span class="n">adj</span><span class="p">.</span><span class="n">first</span><span class="p">;</span>
<a id="__codelineno-11-72" name="__codelineno-11-72" href="#__codelineno-11-72"></a><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="k">auto</span><span class="o">&amp;</span><span class="w"> </span><span class="n">vec</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">adj</span><span class="p">.</span><span class="n">second</span><span class="p">;</span>
<a id="__codelineno-11-73" name="__codelineno-11-73" href="#__codelineno-11-73"></a><span class="w"> </span><span class="n">cout</span><span class="w"> </span><span class="o">&lt;&lt;</span><span class="w"> </span><span class="n">key</span><span class="o">-&gt;</span><span class="n">val</span><span class="w"> </span><span class="o">&lt;&lt;</span><span class="w"> </span><span class="s">&quot;: &quot;</span><span class="p">;</span>
<a id="__codelineno-11-74" name="__codelineno-11-74" href="#__codelineno-11-74"></a><span class="w"> </span><span class="n">PrintUtil</span><span class="o">::</span><span class="n">printVector</span><span class="p">(</span><span class="n">vetsToVals</span><span class="p">(</span><span class="n">vec</span><span class="p">));</span>
<a id="__codelineno-11-75" name="__codelineno-11-75" href="#__codelineno-11-75"></a><span class="w"> </span><span class="p">}</span>
<a id="__codelineno-11-76" name="__codelineno-11-76" href="#__codelineno-11-76"></a><span class="w"> </span><span class="p">}</span>
<a id="__codelineno-11-77" name="__codelineno-11-77" href="#__codelineno-11-77"></a><span class="p">};</span>
<div class="highlight"><span class="filename">graph_adjacency_list.cpp</span><pre><span></span><code><a id="__codelineno-11-1" name="__codelineno-11-1" href="#__codelineno-11-1"></a><span class="p">[</span><span class="k">class</span><span class="p">]{</span><span class="n">GraphAdjList</span><span class="p">}</span><span class="o">-</span><span class="p">[</span><span class="n">func</span><span class="p">]{}</span>
</code></pre></div>
</div>
<div class="tabbed-block">
@ -2746,7 +2670,7 @@
<a id="__codelineno-12-44" name="__codelineno-12-44" href="#__codelineno-12-44"></a> <span class="k">raise</span> <span class="ne">ValueError</span>
<a id="__codelineno-12-45" name="__codelineno-12-45" href="#__codelineno-12-45"></a> <span class="c1"># 在邻接表中删除顶点 vet 对应的链表</span>
<a id="__codelineno-12-46" name="__codelineno-12-46" href="#__codelineno-12-46"></a> <span class="bp">self</span><span class="o">.</span><span class="n">adj_list</span><span class="o">.</span><span class="n">pop</span><span class="p">(</span><span class="n">vet</span><span class="p">)</span>
<a id="__codelineno-12-47" name="__codelineno-12-47" href="#__codelineno-12-47"></a> <span class="c1"># 遍历其顶点的链表,删除所有包含 vet 的边</span>
<a id="__codelineno-12-47" name="__codelineno-12-47" href="#__codelineno-12-47"></a> <span class="c1"># 遍历其顶点的链表,删除所有包含 vet 的边</span>
<a id="__codelineno-12-48" name="__codelineno-12-48" href="#__codelineno-12-48"></a> <span class="k">for</span> <span class="n">vertex</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">adj_list</span><span class="p">:</span>
<a id="__codelineno-12-49" name="__codelineno-12-49" href="#__codelineno-12-49"></a> <span class="k">if</span> <span class="n">vet</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">adj_list</span><span class="p">[</span><span class="n">vertex</span><span class="p">]:</span>
<a id="__codelineno-12-50" name="__codelineno-12-50" href="#__codelineno-12-50"></a> <span class="bp">self</span><span class="o">.</span><span class="n">adj_list</span><span class="p">[</span><span class="n">vertex</span><span class="p">]</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="n">vet</span><span class="p">)</span>
@ -2827,7 +2751,7 @@
<a id="__codelineno-13-65" name="__codelineno-13-65" href="#__codelineno-13-65"></a><span class="w"> </span><span class="p">}</span>
<a id="__codelineno-13-66" name="__codelineno-13-66" href="#__codelineno-13-66"></a><span class="w"> </span><span class="c1">// 在邻接表中删除顶点 vet 对应的链表</span>
<a id="__codelineno-13-67" name="__codelineno-13-67" href="#__codelineno-13-67"></a><span class="w"> </span><span class="nb">delete</span><span class="p">(</span><span class="nx">g</span><span class="p">.</span><span class="nx">adjList</span><span class="p">,</span><span class="w"> </span><span class="nx">vet</span><span class="p">)</span>
<a id="__codelineno-13-68" name="__codelineno-13-68" href="#__codelineno-13-68"></a><span class="w"> </span><span class="c1">// 遍历其顶点的链表,删除所有包含 vet 的边</span>
<a id="__codelineno-13-68" name="__codelineno-13-68" href="#__codelineno-13-68"></a><span class="w"> </span><span class="c1">// 遍历其顶点的链表,删除所有包含 vet 的边</span>
<a id="__codelineno-13-69" name="__codelineno-13-69" href="#__codelineno-13-69"></a><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">list</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">g</span><span class="p">.</span><span class="nx">adjList</span><span class="w"> </span><span class="p">{</span>
<a id="__codelineno-13-70" name="__codelineno-13-70" href="#__codelineno-13-70"></a><span class="w"> </span><span class="nx">DeleteSliceElms</span><span class="p">(</span><span class="nx">list</span><span class="p">,</span><span class="w"> </span><span class="nx">vet</span><span class="p">)</span>
<a id="__codelineno-13-71" name="__codelineno-13-71" href="#__codelineno-13-71"></a><span class="w"> </span><span class="p">}</span>
@ -2904,7 +2828,7 @@
<a id="__codelineno-14-53" name="__codelineno-14-53" href="#__codelineno-14-53"></a><span class="w"> </span><span class="p">}</span>
<a id="__codelineno-14-54" name="__codelineno-14-54" href="#__codelineno-14-54"></a><span class="w"> </span><span class="c1">// 在邻接表中删除顶点 vet 对应的链表</span>
<a id="__codelineno-14-55" name="__codelineno-14-55" href="#__codelineno-14-55"></a><span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">adjList</span><span class="p">.</span><span class="ow">delete</span><span class="p">(</span><span class="nx">vet</span><span class="p">);</span>
<a id="__codelineno-14-56" name="__codelineno-14-56" href="#__codelineno-14-56"></a><span class="w"> </span><span class="c1">// 遍历其顶点的链表,删除所有包含 vet 的边</span>
<a id="__codelineno-14-56" name="__codelineno-14-56" href="#__codelineno-14-56"></a><span class="w"> </span><span class="c1">// 遍历其顶点的链表,删除所有包含 vet 的边</span>
<a id="__codelineno-14-57" name="__codelineno-14-57" href="#__codelineno-14-57"></a><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="kd">let</span><span class="w"> </span><span class="nx">set</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">adjList</span><span class="p">.</span><span class="nx">values</span><span class="p">())</span><span class="w"> </span><span class="p">{</span>
<a id="__codelineno-14-58" name="__codelineno-14-58" href="#__codelineno-14-58"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">index</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">set</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="nx">vet</span><span class="p">);</span>
<a id="__codelineno-14-59" name="__codelineno-14-59" href="#__codelineno-14-59"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">index</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="o">-</span><span class="mf">1</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
@ -2983,7 +2907,7 @@
<a id="__codelineno-15-53" name="__codelineno-15-53" href="#__codelineno-15-53"></a><span class="w"> </span><span class="p">}</span>
<a id="__codelineno-15-54" name="__codelineno-15-54" href="#__codelineno-15-54"></a><span class="w"> </span><span class="c1">// 在邻接表中删除顶点 vet 对应的链表</span>
<a id="__codelineno-15-55" name="__codelineno-15-55" href="#__codelineno-15-55"></a><span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">adjList</span><span class="p">.</span><span class="ow">delete</span><span class="p">(</span><span class="nx">vet</span><span class="p">);</span>
<a id="__codelineno-15-56" name="__codelineno-15-56" href="#__codelineno-15-56"></a><span class="w"> </span><span class="c1">// 遍历其顶点的链表,删除所有包含 vet 的边</span>
<a id="__codelineno-15-56" name="__codelineno-15-56" href="#__codelineno-15-56"></a><span class="w"> </span><span class="c1">// 遍历其顶点的链表,删除所有包含 vet 的边</span>
<a id="__codelineno-15-57" name="__codelineno-15-57" href="#__codelineno-15-57"></a><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="kd">let</span><span class="w"> </span><span class="nx">set</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">adjList</span><span class="p">.</span><span class="nx">values</span><span class="p">())</span><span class="w"> </span><span class="p">{</span>
<a id="__codelineno-15-58" name="__codelineno-15-58" href="#__codelineno-15-58"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">index</span><span class="o">:</span><span class="w"> </span><span class="kt">number</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">set</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="nx">vet</span><span class="p">);</span>
<a id="__codelineno-15-59" name="__codelineno-15-59" href="#__codelineno-15-59"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">index</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="o">-</span><span class="mf">1</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
@ -3072,7 +2996,7 @@
<a id="__codelineno-17-59" name="__codelineno-17-59" href="#__codelineno-17-59"></a><span class="w"> </span><span class="k">throw</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="nf">InvalidOperationException</span><span class="p">();</span>
<a id="__codelineno-17-60" name="__codelineno-17-60" href="#__codelineno-17-60"></a><span class="w"> </span><span class="c1">// 在邻接表中删除顶点 vet 对应的链表</span>
<a id="__codelineno-17-61" name="__codelineno-17-61" href="#__codelineno-17-61"></a><span class="w"> </span><span class="n">adjList</span><span class="p">.</span><span class="n">Remove</span><span class="p">(</span><span class="n">vet</span><span class="p">);</span>
<a id="__codelineno-17-62" name="__codelineno-17-62" href="#__codelineno-17-62"></a><span class="w"> </span><span class="c1">// 遍历其顶点的链表,删除所有包含 vet 的边</span>
<a id="__codelineno-17-62" name="__codelineno-17-62" href="#__codelineno-17-62"></a><span class="w"> </span><span class="c1">// 遍历其顶点的链表,删除所有包含 vet 的边</span>
<a id="__codelineno-17-63" name="__codelineno-17-63" href="#__codelineno-17-63"></a><span class="w"> </span><span class="k">foreach</span><span class="w"> </span><span class="p">(</span><span class="n">List</span><span class="o">&lt;</span><span class="n">Vertex</span><span class="o">&gt;</span><span class="w"> </span><span class="n">list</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">adjList</span><span class="p">.</span><span class="n">Values</span><span class="p">)</span>
<a id="__codelineno-17-64" name="__codelineno-17-64" href="#__codelineno-17-64"></a><span class="w"> </span><span class="p">{</span>
<a id="__codelineno-17-65" name="__codelineno-17-65" href="#__codelineno-17-65"></a><span class="w"> </span><span class="n">list</span><span class="p">.</span><span class="n">Remove</span><span class="p">(</span><span class="n">vet</span><span class="p">);</span>
@ -3152,7 +3076,7 @@
<a id="__codelineno-18-55" name="__codelineno-18-55" href="#__codelineno-18-55"></a> <span class="p">}</span>
<a id="__codelineno-18-56" name="__codelineno-18-56" href="#__codelineno-18-56"></a> <span class="c1">// 在邻接表中删除顶点 vet 对应的链表</span>
<a id="__codelineno-18-57" name="__codelineno-18-57" href="#__codelineno-18-57"></a> <span class="n">adjList</span><span class="p">.</span><span class="n">removeValue</span><span class="p">(</span><span class="n">forKey</span><span class="p">:</span> <span class="n">vet</span><span class="p">)</span>
<a id="__codelineno-18-58" name="__codelineno-18-58" href="#__codelineno-18-58"></a> <span class="c1">// 遍历其顶点的链表,删除所有包含 vet 的边</span>
<a id="__codelineno-18-58" name="__codelineno-18-58" href="#__codelineno-18-58"></a> <span class="c1">// 遍历其顶点的链表,删除所有包含 vet 的边</span>
<a id="__codelineno-18-59" name="__codelineno-18-59" href="#__codelineno-18-59"></a> <span class="k">for</span> <span class="n">key</span> <span class="k">in</span> <span class="n">adjList</span><span class="p">.</span><span class="n">keys</span> <span class="p">{</span>
<a id="__codelineno-18-60" name="__codelineno-18-60" href="#__codelineno-18-60"></a> <span class="n">adjList</span><span class="p">[</span><span class="n">key</span><span class="p">]?.</span><span class="bp">removeAll</span><span class="p">(</span><span class="k">where</span><span class="p">:</span> <span class="p">{</span> <span class="nv">$0</span> <span class="p">==</span> <span class="n">vet</span> <span class="p">})</span>
<a id="__codelineno-18-61" name="__codelineno-18-61" href="#__codelineno-18-61"></a> <span class="p">}</span>

View File

@ -1114,24 +1114,17 @@
<li class="md-nav__item">
<a href="#811" class="md-nav__link">
8.1.1. &nbsp;术语与性质
8.1.1. &nbsp;常用操作
</a>
</li>
<li class="md-nav__item">
<a href="#812" class="md-nav__link">
8.1.2. &nbsp;常用操作
8.1.2. &nbsp;的实现
</a>
</li>
<li class="md-nav__item">
<a href="#813" class="md-nav__link">
8.1.3. &nbsp; 堆的实现
</a>
<nav class="md-nav" aria-label="8.1.3. &nbsp; 堆的实现">
<nav class="md-nav" aria-label="8.1.2. &nbsp; 堆的实现">
<ul class="md-nav__list">
<li class="md-nav__item">
@ -1168,8 +1161,8 @@
</li>
<li class="md-nav__item">
<a href="#814" class="md-nav__link">
8.1.4. &nbsp; 堆常见应用
<a href="#813" class="md-nav__link">
8.1.3. &nbsp; 堆常见应用
</a>
</li>
@ -1752,24 +1745,17 @@
<li class="md-nav__item">
<a href="#811" class="md-nav__link">
8.1.1. &nbsp;术语与性质
8.1.1. &nbsp;常用操作
</a>
</li>
<li class="md-nav__item">
<a href="#812" class="md-nav__link">
8.1.2. &nbsp;常用操作
8.1.2. &nbsp;的实现
</a>
</li>
<li class="md-nav__item">
<a href="#813" class="md-nav__link">
8.1.3. &nbsp; 堆的实现
</a>
<nav class="md-nav" aria-label="8.1.3. &nbsp; 堆的实现">
<nav class="md-nav" aria-label="8.1.2. &nbsp; 堆的实现">
<ul class="md-nav__list">
<li class="md-nav__item">
@ -1806,8 +1792,8 @@
</li>
<li class="md-nav__item">
<a href="#814" class="md-nav__link">
8.1.4. &nbsp; 堆常见应用
<a href="#813" class="md-nav__link">
8.1.3. &nbsp; 堆常见应用
</a>
</li>
@ -1836,7 +1822,7 @@
<h1 id="81">8.1. &nbsp;<a class="headerlink" href="#81" title="Permanent link">&para;</a></h1>
<p>「堆 Heap」是一棵限定条件下的「完全二叉树」。根据成立条件,堆主要分为两种类型:</p>
<p>「堆 Heap」是一种满足特定条件的完全二叉树,可分为两种类型:</p>
<ul>
<li>「大顶堆 Max Heap」任意节点的值 <span class="arithmatex">\(\geq\)</span> 其子节点的值;</li>
<li>「小顶堆 Min Heap」任意节点的值 <span class="arithmatex">\(\leq\)</span> 其子节点的值;</li>
@ -1844,16 +1830,16 @@
<p><img alt="小顶堆与大顶堆" src="../heap.assets/min_heap_and_max_heap.png" /></p>
<p align="center"> Fig. 小顶堆与大顶堆 </p>
<h2 id="811">8.1.1. &nbsp; 堆术语与性质<a class="headerlink" href="#811" title="Permanent link">&para;</a></h2>
<p>堆作为完全二叉树的一个特例,具有以下特性:</p>
<ul>
<li>由于堆是完全二叉树,因此最底层节点靠左填充,其它层节点被填满。</li>
<li>二叉树的根节点对应「堆顶」,底层最靠右节点对应「堆底」。</li>
<li>对于大顶堆 / 小顶堆,堆顶元素(即根节点)的值最大 / 最小</li>
<li>最底层节点靠左填充,其他层的节点被填满。</li>
<li>我们将二叉树的根节点称为「堆顶」,底层最靠右节点称为「堆底」。</li>
<li>对于大顶堆小顶堆,堆顶元素(即根节点)的值分别是最大(最小)的</li>
</ul>
<h2 id="812">8.1.2. &nbsp; 堆常用操作<a class="headerlink" href="#812" title="Permanent link">&para;</a></h2>
<p>值得说明的是,多编程语言提供的是「优先队列 Priority Queue」是一种抽象数据结构,<strong>定义为具有出队优先级的队列</strong></p>
<p>而恰好<strong>的定义与优先队列的操作逻辑完全吻合</strong>,大顶堆就是一个元素从大到小出队的优先队列。从使用角度看,我们可以将「优先队列」和「堆」理解为等价的数据结构。因此,本文与代码对两者不做特别区分,统一使用「堆」来命名。</p>
<p>堆的常用操作见下表,方法名需根据编程语言确定。</p>
<h2 id="811">8.1.1. &nbsp; 堆常用操作<a class="headerlink" href="#811" title="Permanent link">&para;</a></h2>
<p>需要指出的是,多编程语言提供的是「优先队列 Priority Queue」是一种抽象数据结构,定义为具有优先级排序的队列。</p>
<p>实际上<strong>通常用作实现优先队列,大顶堆相当于元素按从大到小顺序出队的优先队列</strong>。从使用角度看,我们可以将「优先队列」和「堆」看作等价的数据结构。因此,本对两者不做特别区分,统一使用「堆」来命名。</p>
<p>堆的常用操作见下表,方法名需根据编程语言确定。</p>
<div class="center-table">
<table>
<thead>
@ -1892,10 +1878,10 @@
</tbody>
</table>
</div>
<p>我们可以直接使用编程语言提供的堆类(或优先队列类)。</p>
<p>在实际应用中,我们可以直接使用编程语言提供的堆类(或优先队列类)。</p>
<div class="admonition tip">
<p class="admonition-title">Tip</p>
<p>类似于排序“从小到大排列”和“从大到小排列”,“大顶堆”和“小顶堆”可仅通过修改 Comparator 来互相转换。</p>
<p>类似于排序算法中的“从小到大排列”和“从大到小排列”,我们可以通过修改 Comparator 来实现“小顶堆”与“大顶堆”之间的转换。</p>
</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">
@ -2143,16 +2129,16 @@
</div>
</div>
</div>
<h2 id="813">8.1.3. &nbsp; 堆的实现<a class="headerlink" href="#813" title="Permanent link">&para;</a></h2>
<p>下文实现的是大顶堆」,若想转换为小顶堆,将所有大小逻辑判断取逆(例如将 <span class="arithmatex">\(\geq\)</span> 替换为 <span class="arithmatex">\(\leq\)</span> 即可,有兴趣的同学可自行实现。</p>
<h2 id="812">8.1.2. &nbsp; 堆的实现<a class="headerlink" href="#812" title="Permanent link">&para;</a></h2>
<p>下文实现的是大顶堆。若要将其转换为小顶堆,只需将所有大小逻辑判断取逆(例如<span class="arithmatex">\(\geq\)</span> 替换为 <span class="arithmatex">\(\leq\)</span> 。感兴趣的读者可以自行实现。</p>
<h3 id="_1">堆的存储与表示<a class="headerlink" href="#_1" title="Permanent link">&para;</a></h3>
<p>在二叉树章节我们学过,「完全二叉树非常适合使用「数组来表示,而堆恰好是一完全二叉树,<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>我们在二叉树章节中学习到,完全二叉树非常适合数组来表示。由于堆正是一完全二叉树,<strong>我们采用数组来存储</strong></p>
<p>使用数组表示二叉树时,元素代表节点值,索引代表节点在二叉树中的位置<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><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-content">
<div class="tabbed-block">
@ -2321,7 +2307,7 @@
</div>
</div>
<h3 id="_2">访问堆顶元素<a class="headerlink" href="#_2" title="Permanent link">&para;</a></h3>
<p>堆顶元素二叉树的根节点,即列表首元素。</p>
<p>堆顶元素即为二叉树的根节点,也就是列表的首个元素。</p>
<div class="tabbed-set tabbed-alternate" data-tabs="3:10"><input checked="checked" id="__tabbed_3_1" name="__tabbed_3" type="radio" /><input id="__tabbed_3_2" name="__tabbed_3" type="radio" /><input id="__tabbed_3_3" name="__tabbed_3" type="radio" /><input id="__tabbed_3_4" name="__tabbed_3" type="radio" /><input id="__tabbed_3_5" name="__tabbed_3" type="radio" /><input id="__tabbed_3_6" name="__tabbed_3" type="radio" /><input id="__tabbed_3_7" name="__tabbed_3" type="radio" /><input id="__tabbed_3_8" name="__tabbed_3" type="radio" /><input id="__tabbed_3_9" name="__tabbed_3" type="radio" /><input id="__tabbed_3_10" name="__tabbed_3" type="radio" /><div class="tabbed-labels"><label for="__tabbed_3_1">Java</label><label for="__tabbed_3_2">C++</label><label for="__tabbed_3_3">Python</label><label for="__tabbed_3_4">Go</label><label for="__tabbed_3_5">JavaScript</label><label for="__tabbed_3_6">TypeScript</label><label for="__tabbed_3_7">C</label><label for="__tabbed_3_8">C#</label><label for="__tabbed_3_9">Swift</label><label for="__tabbed_3_10">Zig</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
@ -2394,8 +2380,8 @@
</div>
</div>
<h3 id="_3">元素入堆<a class="headerlink" href="#_3" title="Permanent link">&para;</a></h3>
<p>给定元素 <code>val</code> ,我们先将其添加到堆底。添加后,由于 <code>val</code> 可能大于堆中其元素,此时堆的成立条件可能已被破坏,<strong>因此需要修复从插入节点到根节点这条路径上的各个节点</strong>操作被称为「堆化 Heapify」。</p>
<p>考虑从入堆节点开始,<strong>从底至顶执行堆化</strong>。具体地,比较插入节点与其父节点的值,插入节点更大则将它们交换;并循环以上操作,从底至顶修复堆中的各个节点直至越过根节点时结束,或当遇到无需交换的节点时提前结束。</p>
<p>给定元素 <code>val</code> ,我们先将其添加到堆底。添加后,由于 val 可能大于堆中其元素,堆的成立条件可能已被破坏。因此<strong>需要修复从插入节点到根节点路径上的各个节点</strong>这个操作被称为「堆化 Heapify」。</p>
<p>考虑从入堆节点开始,<strong>从底至顶执行堆化</strong>。具体来说,我们比较插入节点与其父节点的值,如果插入节点更大则将它们交换。然后继续执行此操作,从底至顶修复堆中的各个节点直至越过根节点遇到无需交换的节点时结束。</p>
<div class="tabbed-set tabbed-alternate" data-tabs="4:6"><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" /><div class="tabbed-labels"><label for="__tabbed_4_1">&lt;1&gt;</label><label for="__tabbed_4_2">&lt;2&gt;</label><label for="__tabbed_4_3">&lt;3&gt;</label><label for="__tabbed_4_4">&lt;4&gt;</label><label for="__tabbed_4_5">&lt;5&gt;</label><label for="__tabbed_4_6">&lt;6&gt;</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
@ -2418,7 +2404,7 @@
</div>
</div>
</div>
<p>设节点总数为 <span class="arithmatex">\(n\)</span> ,则树的高度为 <span class="arithmatex">\(O(\log n)\)</span> 易得堆化操作的循环轮数最多为 <span class="arithmatex">\(O(\log n)\)</span> <strong>因而元素入堆操作的时间复杂度为 <span class="arithmatex">\(O(\log n)\)</span></strong></p>
<p>设节点总数为 <span class="arithmatex">\(n\)</span> ,则树的高度为 <span class="arithmatex">\(O(\log n)\)</span> 。由此可知,堆化操作的循环轮数最多为 <span class="arithmatex">\(O(\log n)\)</span> <strong>元素入堆操作的时间复杂度为 <span class="arithmatex">\(O(\log n)\)</span></strong></p>
<div class="tabbed-set tabbed-alternate" data-tabs="5:10"><input checked="checked" id="__tabbed_5_1" name="__tabbed_5" type="radio" /><input id="__tabbed_5_2" name="__tabbed_5" type="radio" /><input id="__tabbed_5_3" name="__tabbed_5" type="radio" /><input id="__tabbed_5_4" name="__tabbed_5" type="radio" /><input id="__tabbed_5_5" name="__tabbed_5" type="radio" /><input id="__tabbed_5_6" name="__tabbed_5" type="radio" /><input id="__tabbed_5_7" name="__tabbed_5" type="radio" /><input id="__tabbed_5_8" name="__tabbed_5" type="radio" /><input id="__tabbed_5_9" name="__tabbed_5" type="radio" /><input id="__tabbed_5_10" name="__tabbed_5" type="radio" /><div class="tabbed-labels"><label for="__tabbed_5_1">Java</label><label for="__tabbed_5_2">C++</label><label for="__tabbed_5_3">Python</label><label for="__tabbed_5_4">Go</label><label for="__tabbed_5_5">JavaScript</label><label for="__tabbed_5_6">TypeScript</label><label for="__tabbed_5_7">C</label><label for="__tabbed_5_8">C#</label><label for="__tabbed_5_9">Swift</label><label for="__tabbed_5_10">Zig</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
@ -2656,13 +2642,13 @@
</div>
</div>
<h3 id="_4">堆顶元素出堆<a class="headerlink" href="#_4" title="Permanent link">&para;</a></h3>
<p>堆顶元素是二叉树根节点,即列表首元素如果我们直接将首元素从列表中删除,则二叉树中所有节点都会随之发生移位(索引发生变化,这后续使用堆化修复就很麻烦了。为了尽量减少元素索引变动,采取以下操作步骤:</p>
<p>堆顶元素是二叉树根节点,即列表首元素如果我们直接从列表中删除首元素,那么二叉树中所有节点的索引都会发生变化,这将使得后续使用堆化修复变得困难。为了尽量减少元素索引变动,我们采取以下操作步骤:</p>
<ol>
<li>交换堆顶元素与堆底元素(即交换根节点与最右叶节点);</li>
<li>交换完成后,将堆底从列表中删除(注意,因为已经交换,实际上删除的是原来的堆顶元素);</li>
<li>交换完成后,将堆底从列表中删除(注意,由于已经交换,实际上删除的是原来的堆顶元素);</li>
<li>从根节点开始,<strong>从顶至底执行堆化</strong></li>
</ol>
<p>顾名思义,<strong>从顶至底堆化的操作方向与从底至顶堆化相反</strong>,我们比较根节点的值与其两个子节点的值,将最大的子节点与根节点执行交换,并循环以上操作,直到越过叶节点时结束,或当遇到无需交换的节点时提前结束。</p>
<p>顾名思义,<strong>从顶至底堆化的操作方向与从底至顶堆化相反</strong>,我们根节点的值与其两个子节点的值进行比较,将最大的子节点与根节点交换;然后循环执行此操作,直到越过叶节点遇到无需交换的节点时结束。</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">&lt;1&gt;</label><label for="__tabbed_6_2">&lt;2&gt;</label><label for="__tabbed_6_3">&lt;3&gt;</label><label for="__tabbed_6_4">&lt;4&gt;</label><label for="__tabbed_6_5">&lt;5&gt;</label><label for="__tabbed_6_6">&lt;6&gt;</label><label for="__tabbed_6_7">&lt;7&gt;</label><label for="__tabbed_6_8">&lt;8&gt;</label><label for="__tabbed_6_9">&lt;9&gt;</label><label for="__tabbed_6_10">&lt;10&gt;</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
@ -2697,7 +2683,7 @@
</div>
</div>
</div>
<p>与元素入堆操作似,<strong>堆顶元素出堆操作的时间复杂度为 <span class="arithmatex">\(O(\log n)\)</span></strong></p>
<p>与元素入堆操作似,堆顶元素出堆操作的时间复杂度<span class="arithmatex">\(O(\log 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-content">
<div class="tabbed-block">
@ -3038,11 +3024,11 @@
</div>
</div>
</div>
<h2 id="814">8.1.4. &nbsp; 堆常见应用<a class="headerlink" href="#814" title="Permanent link">&para;</a></h2>
<h2 id="813">8.1.3. &nbsp; 堆常见应用<a class="headerlink" href="#813" title="Permanent link">&para;</a></h2>
<ul>
<li><strong>优先队列</strong>。堆常作为实现优先队列的首选数据结构,入队和出队操作时间复杂度为 <span class="arithmatex">\(O(\log n)\)</span> ,建队操作为 <span class="arithmatex">\(O(n)\)</span> 非常高效。</li>
<li><strong>堆排序</strong>给定一组数据,我们使用其建堆,并依次全部弹出,则可以得到有序序列。当然,堆排序一般无需弹出元素,仅需每轮将堆顶元素交换至数组尾部并小堆的长度即可</li>
<li><strong>获取最大的 <span class="arithmatex">\(k\)</span> 个元素</strong>。这既是一经典算法题目,也是一种常见应用,例如选热度前 10 的新闻作为微博热搜,选取前 10 销量的商品等。</li>
<li><strong>优先队列</strong>:堆通常作为实现优先队列的首选数据结构,入队和出队操作时间复杂度<span class="arithmatex">\(O(\log n)\)</span> 建队操作为 <span class="arithmatex">\(O(n)\)</span> 这些操作都非常高效。</li>
<li><strong>堆排序</strong>给定一组数据,我们可以用它们建立一个堆,然后依次将所有元素弹出,从而得到一个有序序列。当然,堆排序的实现方法并不需要弹出元素,而是每轮将堆顶元素交换至数组尾部并小堆的长度。</li>
<li><strong>获取最大的 <span class="arithmatex">\(k\)</span> 个元素</strong>:这是一经典算法问题,同时也是一种典型应用,例如选热度前 10 的新闻作为微博热搜,选取销量前 10 的商品等。</li>
</ul>

View File

@ -1681,12 +1681,12 @@
<h1 id="83">8.3. &nbsp; 小结<a class="headerlink" href="#83" title="Permanent link">&para;</a></h1>
<ul>
<li>堆是一棵限定条件下的完全二叉树,根据成立条件可分为大顶堆和小顶堆。大(小)顶堆的堆顶元素最大(小)。</li>
<li>优先队列定义为一种具有出队优先级的队列。堆是实现优先队列的最常用数据结构</li>
<li>堆的常用操作对应时间复杂度元素入堆 <span class="arithmatex">\(O(\log n)\)</span> 、堆顶元素出堆 <span class="arithmatex">\(O(\log n)\)</span> 访问堆顶元素 <span class="arithmatex">\(O(1)\)</span> 等。</li>
<li>完全二叉树非常适合用数组表示,因此我们一般用数组来存储堆。</li>
<li>堆化操作用于修复堆的特性,在入堆和出堆操作中都会使用到。</li>
<li>输入 <span class="arithmatex">\(n\)</span> 个元素并建堆的时间复杂度可以优化至 <span class="arithmatex">\(O(n)\)</span> ,非常高效。</li>
<li>堆是一棵完全二叉树,根据成立条件可分为大顶堆和小顶堆。大(小)顶堆的堆顶元素最大(小)</li>
<li>优先队列定义具有出队优先级的队列,通常使用堆来实现</li>
<li>堆的常用操作及其对应时间复杂度包括:元素入堆 <span class="arithmatex">\(O(\log n)\)</span> 、堆顶元素出堆 <span class="arithmatex">\(O(\log n)\)</span> 访问堆顶元素 <span class="arithmatex">\(O(1)\)</span> 等。</li>
<li>完全二叉树非常适合用数组表示,因此我们通常使用数组来存储堆。</li>
<li>堆化操作用于维护堆的性质,在入堆和出堆操作中都会用到。</li>
<li>输入 <span class="arithmatex">\(n\)</span> 个元素并建堆的时间复杂度可以优化至 <span class="arithmatex">\(O(n)\)</span> ,非常高效。</li>
</ul>

View File

@ -1680,15 +1680,15 @@
<h1 id="11">1.1. &nbsp; 算法无处不在<a class="headerlink" href="#11" title="Permanent link">&para;</a></h1>
<p>听到“算法”这个词,我们一般会联想到数学。实际上,大多数算法并不包含复杂数学,而更像是在考察基本逻辑,这些逻辑在我们日常生活中处处可见。</p>
<p>在正式介绍算法之前,我想告诉你一件有趣的事<strong>其实,你在过去已经学会了多算法,并且已经习惯将们应用到日常生活中</strong>接下来,我将介绍两个具体例子来证。</p>
<p><strong>例一:积木</strong>。一套积木,除了许多件之外,还会附送详细的装说明书。我们按照说明书一步步操作,即可拼出复杂的积木模型。</p>
<p>如果从数据结构与算法的角度看,大大小小的「积木」就是数据结构,而「拼装说明书上的一系列步骤是算法。</p>
<p><strong>例二:查字典</strong>。在字典,每个汉字都有一个对应的拼音,而字典是按照拼音的英文字母顺序排列的。假设需要在字典中查询任意一个拼音首字母为 <span class="arithmatex">\(r\)</span> 的字,一般我们会这样</p>
<p>当我们听到“算法”这个词时,很自然地会想到数学。然而实际上,许多算法并不涉及复杂数学,而是更多地依赖于基本逻辑,这些逻辑在我们日常生活中处处可见。</p>
<p>在正式探讨算法之前,有一个有趣的事实值得分享<strong>实际上,你已经学会了多算法,并习惯将们应用到日常生活中</strong>下面,我将两个具体例子来证实这一点</p>
<p><strong>例一:组装积木</strong>。一套积木,除了包含许多件之外,还附有详细的装说明书。我们按照说明书一步步操作,就能组装出精美的积木模型。</p>
<p>从数据结构与算法的角度看,积木的各种形状和连接方式代表数据结构,而装说明书上的一系列步骤是算法。</p>
<p><strong>例二:查字典</strong>。在字典,每个汉字都对应一个拼音,而字典是按照拼音的英文字母顺序排列的。假设我们需要查找一个拼音首字母为 <span class="arithmatex">\(r\)</span> 的字,通常会这样操作</p>
<ol>
<li>开字典大致一半页数的位置,查看此页的首字母是什么(假设为 <span class="arithmatex">\(m\)</span> </li>
<li>由于在英文字母表中 <span class="arithmatex">\(r\)</span> <span class="arithmatex">\(m\)</span> 的后面,因此应排除字典前半部分,查找范围仅剩后半部分;</li>
<li>循环执行步骤 1-2 ,直找到拼音首字母为 <span class="arithmatex">\(r\)</span> 的页码时终止。</li>
<li>开字典一半页数,查看该页首字母是什么(假设为 <span class="arithmatex">\(m\)</span> </li>
<li>由于在英文字母表中 <span class="arithmatex">\(r\)</span> 位于 <span class="arithmatex">\(m\)</span> 之后,所以排除字典前半部分,查找范围缩小到后半部分;</li>
<li>不断重复步骤 1-2 ,直找到拼音首字母为 <span class="arithmatex">\(r\)</span> 的页码止。</li>
</ol>
<div class="tabbed-set tabbed-alternate" data-tabs="1:5"><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" /><div class="tabbed-labels"><label for="__tabbed_1_1">&lt;1&gt;</label><label for="__tabbed_1_2">&lt;2&gt;</label><label for="__tabbed_1_3">&lt;3&gt;</label><label for="__tabbed_1_4">&lt;4&gt;</label><label for="__tabbed_1_5">&lt;5&gt;</label></div>
<div class="tabbed-content">
@ -1709,11 +1709,11 @@
</div>
</div>
</div>
<p>查字典这个小学生的标配技能,实际上就是大名鼎鼎的「二分查找」。从数据结构角度,我们可以字典看作是一个已排序的「数组」;从算法角度,我们可将上述查字典的一系列指令看作是「二分查找」算法。</p>
<p>小到烹饪一道菜大到星际航行,几乎所有问题的解决都离不开算法。计算机的出现使我们可以通过编程将数据结构存储在内存中,也可以编写代码调用 CPU, GPU 执行算法,从而将生活中的问题搬运到计算机中,更加高效地解决各式各样的复杂问题。</p>
<p>字典这个小学生必备技能,实际上就是著名的「二分查找」。从数据结构角度,我们可以字典视为一个已排序的「数组」;从算法角度,我们可将上述查字典的一系列操作看作是「二分查找」算法。</p>
<p>小到烹饪一道菜大到星际航行,几乎所有问题的解决都离不开算法。计算机的出现使我们能够通过编程将数据结构存储在内存中,同时编写代码调用 CPU GPU 执行算法。这样一来,我们就能把生活中的问题转移到计算机上,以更高效的方式解决各种复杂问题。</p>
<div class="admonition tip">
<p class="admonition-title">Tip</p>
<p>读到这里,如果你感到对数据结构、算法、数组二分查找等此类概念一知半解,那么太好了!因为这正是本书存在的价值,接下来,本书将一步步引导你入数据结构与算法的知识殿堂。</p>
<p>阅读至此,如果你对数据结构、算法、数组二分查找等概念仍感到一知半解,那么太好了!因为这正是本书存在的意义。接下来,本书将一步步引导你入数据结构与算法的知识殿堂。</p>
</div>

View File

@ -1681,11 +1681,11 @@
<h1 id="13">1.3. &nbsp; 小结<a class="headerlink" href="#13" title="Permanent link">&para;</a></h1>
<ul>
<li>算法在生活中随处可见,并不高深莫测。我们已经不知不觉学习许多“算法”,用解决生活中大小小的问题。</li>
<li>查字典的原理二分查找算法一致。二分体现分而治之的重要算法思想。</li>
<li>算法是在有限时间内解决特定问题的一组指令或操作步骤,数据结构是计算机中组织存储数据的方式。</li>
<li>数据结构与算法两者紧密联系。数据结构是算法的底座,算法是发挥数据结构的舞台。</li>
<li>乐高积木对应数据,积木形状和连接形式对应数据结构,拼装积木的流程步骤对应算法。</li>
<li>算法在日常生活中无处不在,并不是遥不可及的高深知识。实际上,我们已经不知不觉学习许多“算法”,用解决生活中大小问题。</li>
<li>字典的原理二分查找算法一致。二分查找体现分而治之的重要算法思想。</li>
<li>算法是在有限时间内解决特定问题的一组指令或操作步骤,数据结构是计算机中组织存储数据的方式。</li>
<li>数据结构与算法紧密相连。数据结构是算法的基石,而算法是发挥数据结构作用的舞台。</li>
<li>乐高积木对应数据,积木形状和连接方式代表数据结构,拼装积木的步骤对应算法。</li>
</ul>

View File

@ -1757,30 +1757,29 @@
<h2 id="121">1.2.1. &nbsp; 算法定义<a class="headerlink" href="#121" title="Permanent link">&para;</a></h2>
<p>「算法 Algorithm」是在有限时间内解决特定问题的一组指令或操作步骤。算法具有以下特性</p>
<ul>
<li>问题是明确的,需要拥有明确的输入和输出定义。</li>
<li>解具有确定性,即给定相同输入时,输出一定相同。</li>
<li>具有可行性,在有限步骤、有限时间、有限内存空间下完成。</li>
<li>独立于编程语言,即可用多种语言实现。</li>
<li>问题是明确的,具有清晰的输入和输出定义。</li>
<li>解具有确定性,即给定相同输入时,输出始终相同。</li>
<li>具有可行性,在有限步骤、时间和内存空间下完成。</li>
</ul>
<h2 id="122">1.2.2. &nbsp; 数据结构定义<a class="headerlink" href="#122" title="Permanent link">&para;</a></h2>
<p>「数据结构 Data Structure」是计算机中组织存储数据的方式。为了提高数据存储和操作性能,数据结构的设计原则有</p>
<p>「数据结构 Data Structure」是计算机中组织存储数据的方式。为了提高数据存储和操作性能,数据结构的设计目标包括</p>
<ul>
<li>空间占用尽可能小,节省计算机内存。</li>
<li>数据操作尽量快,包括数据访问、添加、删除、更新等。</li>
<li>提供简洁的数据表示和逻辑信息,以便算法高效运行。</li>
<li>空间占用尽量减少,节省计算机内存。</li>
<li>数据操作尽可能快速,涵盖数据访问、添加、删除、更新等。</li>
<li>提供简洁的数据表示和逻辑信息,以利于算法高效运行。</li>
</ul>
<p>数据结构设计是一个充满权衡的过程,这意味着如果获得某方面优势,往往需要在另一方面出妥协。例如,链表相于数组,数据添加删除操作更加便,但牺牲了数据访问速度;图相于链表,提供了更的逻辑信息,但需要占用更的内存空间。</p>
<p>数据结构设计是一个充满权衡的过程,这意味着要在某方面取得优势,往往需要在另一方面出妥协。例如,链表相于数组,数据添加删除操作更加便,但牺牲了数据访问速度;图相于链表,提供了更丰富的逻辑信息,但需要占用更的内存空间。</p>
<h2 id="123">1.2.3. &nbsp; 数据结构与算法的关系<a class="headerlink" href="#123" title="Permanent link">&para;</a></h2>
<p>「数据结构」与「算法」高度相关紧密嵌合的,体现在:</p>
<p>「数据结构」与「算法」高度相关紧密结合,具体表现在:</p>
<ul>
<li>数据结构是算法的底座。数据结构为算法提供结构化存储的数据,以及操作数据的对应方法。</li>
<li>算法是数据结构发挥的舞台。数据结构仅存储数据信息,结合算法才解决特定问题。</li>
<li>算法有对应最优的数据结构。给定算法,一般可基于不同的数据结构实现,最终执行效率往往相差很大。</li>
<li>数据结构是算法的基石。数据结构为算法提供结构化存储的数据,以及用于操作数据的方法。</li>
<li>算法是数据结构发挥的舞台。数据结构本身仅存储数据信息,通过结合算法才解决特定问题。</li>
<li>特定算法通常有对应最优的数据结构。算法通常可以基于不同的数据结构进行实现,最终执行效率可能相差很大。</li>
</ul>
<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">
<table>
<thead>
@ -1809,9 +1808,10 @@
</tbody>
</table>
</div>
<p>值得注意的是,数据结构与算法独立于编程语言。正因如此,本书得以提供多种编程语言的实现。</p>
<div class="admonition tip">
<p class="admonition-title">约定俗成的简称</p>
<p>在实际讨论,我们通常会将「数据结构与算法」直接简称为「算法」。例如,我们熟称的 LeetCode 算法题目,实际上同时考察了数据结构和算法两部分知识。</p>
<p>在实际讨论,我们通常会将「数据结构与算法」简称为「算法」。例如,众所周知的 LeetCode 算法题目,实际上同时考察了数据结构和算法两方面的知识。</p>
</div>

View File

@ -1752,42 +1752,42 @@
<h1 id="01">0.1. &nbsp; 关于本书<a class="headerlink" href="#01" title="Permanent link">&para;</a></h1>
<p>本项目致力于构建一本开源免费、新手友好的数据结构与算法入门</p>
<p>本项目旨在创建一本开源免费、新手友好的数据结构与算法入门教程</p>
<ul>
<li>全书采用动画图解,结构化地讲解数据结构与算法知识,内容清晰易懂、学习曲线平滑;</li>
<li>算法源代码皆可一键运行,支持 Java, C++, Python, Go, JS, TS, C#, Swift, Zig 等语言;</li>
<li>鼓励读者在章节讨论区互帮互助、共同进步,提问与评论一般能在两日内得到回复;</li>
<li>鼓励读者在章节讨论区互帮互助、共同进步,提问与评论通常可在两日内得到回复;</li>
</ul>
<h2 id="011">0.1.1. &nbsp; 读者对象<a class="headerlink" href="#011" title="Permanent link">&para;</a></h2>
<p>如果您是「算法初学者」,完全没有接触过算法,或者已经有少量刷题,对数据结构与算法有朦胧的理解,在会与不会之间反复横跳,那么这本书是为你而写</p>
<p>如果您是「算法老手」,已经积累一定刷题量,接触过大多数题型,那么本书可以帮助你回顾与梳理算法知识体系,仓库源代码可以被当作“刷题工具库”或“算法字典”来使用。</p>
<p>如果您是「算法大佬」,希望可以得到你的宝贵意见建议,或者<a href="https://www.hello-algo.com/chapter_appendix/contribution/">一起参与创作</a></p>
<p>您是「算法初学者」,从未接触过算法,或者已经有一些刷题经验,对数据结构与算法有模糊的认识,在会与不会之间反复横跳,那么这本书是为您量身定制</p>
<p>如果您是「算法老手」,已经积累一定刷题量,熟悉大部分题型,那么本书可助您回顾与梳理算法知识体系,仓库源代码可以被当作“刷题工具库”或“算法字典”来使用。</p>
<p>您是「算法专家」,我们期待收到您的宝贵建议,或者<a href="https://www.hello-algo.com/chapter_appendix/contribution/">一起参与创作</a></p>
<div class="admonition success">
<p class="admonition-title">前置条件</p>
<p>您需要至少具备任一语言的编程基础,能够阅读和编写简单代码。</p>
</div>
<h2 id="012">0.1.2. &nbsp; 内容结构<a class="headerlink" href="#012" title="Permanent link">&para;</a></h2>
<p>本书主要内容</p>
<p>本书主要内容包括</p>
<ul>
<li><strong>复杂度分析</strong>:数据结构与算法的评价维度、算法效率的评估方法。时间复杂度、空间复杂度,包括推算方法、常见类型、示例等。</li>
<li><strong>数据结构</strong>:常用的基本数据类型,数据在内存中的存储式、数据结构分类方法。数组、链表、栈、队列、散列表、树、堆、图等数据结构,内容包括定义、优劣势、常用操作、常见类型、典型应用、实现方法等。</li>
<li><strong>算法</strong>:查找算法、排序算法、搜索与回溯、动态规划、分治算法,内容包括定义、使用场景、优劣势、时空效率、实现方法、示例题目等。</li>
<li><strong>数据结构</strong>:常基本数据类型,数据在内存中的存储式、数据结构分类方法。涉及数组、链表、栈、队列、散列表、树、堆、图等数据结构,内容包括定义、优缺点、常用操作、常见类型、典型应用、实现方法等。</li>
<li><strong>算法</strong>:查找算法、排序算法、搜索与回溯、动态规划、分治算法,内容涵盖定义、用场景、优缺点、时空效率、实现方法、示例题目等。</li>
</ul>
<p><img alt="Hello 算法内容结构" src="../about_the_book.assets/hello_algo_mindmap.png" /></p>
<p align="center"> Fig. Hello 算法内容结构 </p>
<h2 id="013">0.1.3. &nbsp; 致谢<a class="headerlink" href="#013" title="Permanent link">&para;</a></h2>
<p>本书的成书过程中,我得了许多人的帮助,包括但不限于:</p>
<p>本书的创作过程中,我得了许多人的帮助,包括但不限于:</p>
<ul>
<li>感谢我在公司的导师李汐博士,在一次畅谈时您告诉我“觉得应该做就去做”,坚定了我写这本书的决心。</li>
<li>感谢我的女朋友泡泡担任本书的首位读者,从算法小白的角提出许多建议,使本书更适合初学者来阅读。</li>
<li>感谢腾宝、琦宝、飞宝为本书起了个好听又有梗名字,直接唤起我最初敲下第一行代码 "Hello World!" 的回忆。</li>
<li>感谢苏潼为本书设计了封面和 LOGO ,在我的强迫症下前后多次帮忙修改,谢谢你的耐心</li>
<li>感谢 @squidfunk 给出的写作排版建议,以及优秀开源项目 <a href="https://github.com/squidfunk/mkdocs-material/tree/master">Material-for-MkDocs</a></li>
<li>感谢我在公司的导师李汐博士,在深入交谈中您鼓励我“行动起来”,坚定了我写这本书的决心。</li>
<li>感谢我的女朋友泡泡作为本书的首位读者,从算法小白的角提出许多宝贵建议,使本书更适合新手阅读。</li>
<li>感谢腾宝、琦宝、飞宝为本书起了一个富有创意的名字,唤起大家写下第一行代码 "Hello World!" 的美好回忆。</li>
<li>感谢苏潼为本书设计了精美的封面和 LOGO在我的强迫症下多次耐心修改</li>
<li>感谢 @squidfunk 提供的写作排版建议,以及杰出的开源项目 <a href="https://github.com/squidfunk/mkdocs-material/tree/master">Material-for-MkDocs</a></li>
</ul>
<p>本书鼓励“手脑并用”的学习方式,在这点上受到了《动手学深度学习》很大影响,也在此向各位同学强烈推荐这本著作,包括<a href="https://github.com/d2l-ai/d2l-zh">中文版</a><a href="https://github.com/d2l-ai/d2l-en">英文版</a><a href="https://space.bilibili.com/1567748478">李沐老师 bilibili 主页</a></p>
<p>在写作过程中,我阅读了许多数据结构与算法的教材与文章,这些著作为本书作出了很好的榜样,保证了本书内容的正确性与质量,感谢各位老师与前辈的精彩创作!</p>
<p>感谢父母,你们一的支持与鼓励给了我自由度来做这些有趣的事。</p>
<p>在写作过程中,我阅读了许多关于数据结构与算法的教材和文章。这些作品为本书提供了优秀的范本,确保了本书内容的准确性与品质。在此感谢所有老师和前辈们的杰出贡献!</p>
<p>本书倡导“手脑并用”的学习方法,在此方面深受《动手学深度学习》的启发。在此向各位读者强烈推荐这本优秀著作,包括<a href="https://github.com/d2l-ai/d2l-zh">中文版</a><a href="https://github.com/d2l-ai/d2l-en">英文版</a><a href="https://space.bilibili.com/1567748478">李沐老师 bilibili 主页</a></p>
<p>衷心感谢我的父母,正是你们一直以来的支持与鼓励,让我有机会做这些有趣的事。</p>

View File

@ -1782,22 +1782,25 @@
<h1 id="02">0.2. &nbsp; 如何使用本书<a class="headerlink" href="#02" title="Permanent link">&para;</a></h1>
<p>建议通读本节内容,以获取最佳阅读体验。</p>
<div class="admonition tip">
<p class="admonition-title">Tip</p>
<p>为了获得最佳的阅读体验,建议您通读本节内容。</p>
</div>
<h2 id="021">0.2.1. &nbsp; 算法学习路线<a class="headerlink" href="#021" title="Permanent link">&para;</a></h2>
<p>总体上看,我认为可将学习数据结构与算法的过程分为三个阶段</p>
<p>总体上看,我们可以将学习数据结构与算法的过程分为三个阶段</p>
<ol>
<li><strong>算法入门</strong>。熟悉各种数据结构的特点用法,学习各种算法的原理、流程、用途效率等。</li>
<li><strong>刷算法题</strong>可以先从热门题开刷,推荐<a href="https://leetcode.cn/problem-list/xb9nqhhg/">剑指 Offer</a><a href="https://leetcode.cn/problem-list/2cktkvj/">LeetCode Hot 100</a>,先积累至少 100 道题,熟悉大多数的算法问题。刚开始刷题时,“遗忘”是最大的困扰点,但这是很正常的,请不要担心。学习中有一种概念叫“周期性回顾”,同一道题隔段时间做一次,在重复 3 轮以上后,往往就能牢记于心了</li>
<li><strong>搭建知识体系</strong>。在学习方面,可以阅读算法专栏文章、解题框架算法教材,不断丰富知识体系。在刷题方面,可以开始采用进阶刷题方案,例如按专题分类、一题多解、一解多题等,相关刷题心得可以在各个社区找到。</li>
<li><strong>算法入门</strong>我们需要熟悉各种数据结构的特点用法,学习不同算法的原理、流程、用途效率等方面内容</li>
<li><strong>刷算法题</strong>建议从热门题开刷,<a href="https://leetcode.cn/problem-list/xb9nqhhg/">剑指 Offer</a><a href="https://leetcode.cn/problem-list/2cktkvj/">LeetCode Hot 100</a>,先积累至少 100 道题,熟悉主流的算法问题。初次刷题时,“知识遗忘”可能是一个挑战,但请放心,这是很正常的。我们可以按照“艾宾浩斯遗忘曲线”来复习题目,通常在进行 3-5 轮的重复后,就能将其牢记在心</li>
<li><strong>搭建知识体系</strong>。在学习方面,我们可以阅读算法专栏文章、解题框架算法教材,不断丰富知识体系。在刷题方面,可以尝试采用进阶刷题策略,如按专题分类、一题多解、一解多题等,相关刷题心得可以在各个社区找到。</li>
</ol>
<p>作为一本入门教程,<strong>本书内容主要对应“第一阶段”</strong>,致力于帮助你更高效地开展第二、三阶段的学习。</p>
<p>作为一本入门教程,本书内容主要涵盖“第一阶段”,旨在帮助你更高效地展开第二和第三阶段的学习。</p>
<p><img alt="算法学习路线" src="../suggestions.assets/learning_route.png" /></p>
<p align="center"> Fig. 算法学习路线 </p>
<h2 id="022">0.2.2. &nbsp; 行文风格约定<a class="headerlink" href="#022" title="Permanent link">&para;</a></h2>
<p>标题后标注 <code>*</code> 的是选读章节,内容相对难。如果你的时间有限,建议可以先跳过。</p>
<p>文章中的重要名词会用 <code>括号</code> 标注,例如 <code>「数组 Array」</code>建议记住这些名词,包括英文翻译,以便后续阅读文献时使用。</p>
<p>重点内容、总起句、总结句会被 <strong>加粗</strong> ,此类文字值得特别关注。</p>
<p>标题后标注 <code>*</code> 的是选读章节,内容相对难。如果你的时间有限,建议可以先跳过。</p>
<p>文章中的重要名词会用 <code> </code> 括号标注,例如 <code>「数组 Array」</code>请务必记住这些名词,包括英文翻译,以便后续阅读文献时使用。</p>
<p><strong>加粗的文字</strong> 表示重点内容或总结性语句,这类文字值得特别关注。</p>
<p>专有名词和有特指含义的词句会使用 <code>“双引号”</code> 标注,以避免歧义。</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>
@ -1913,35 +1916,32 @@
</div>
</div>
<h2 id="023">0.2.3. &nbsp; 在动画图解中高效学习<a class="headerlink" href="#023" title="Permanent link">&para;</a></h2>
<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 align="center"> Fig. 动画图解示例 </p>
<h2 id="024">0.2.4. &nbsp; 在代码实践中加深理解<a class="headerlink" href="#024" title="Permanent link">&para;</a></h2>
<p>本书的配套代码托管在<a href="https://github.com/krahets/hello-algo">GitHub 仓库</a><strong>源代码包含详细注释,有测试样例,可直接运行</strong></p>
<ul>
<li>若学习时间紧张,<strong>建议至少将所有代码通读并运行一遍</strong></li>
<li>若时间允许,<strong>强烈建议对照着代码自己敲一遍</strong>。相比于读代码,写代码的过程往往能带来新的收获。</li>
</ul>
<p>本书的配套代码托管在<a href="https://github.com/krahets/hello-algo">GitHub 仓库</a><strong>源代码包含详细注释,并附有测试样例,可直接运行</strong></p>
<p>如果学习时间有限,建议你至少通读并运行所有代码。如果时间充裕,<strong>建议参照代码自行敲一遍</strong>。与仅阅读代码相比,编写代码的过程往往能带来更多收获。</p>
<p><img alt="运行代码示例" src="../suggestions.assets/running_code.gif" /></p>
<p align="center"> Fig. 运行代码示例 </p>
<p><strong>第一步:安装本地编程环境</strong>。参照<a href="https://www.hello-algo.com/chapter_appendix/installation/">附录教程</a>,如果已有可直接跳过</p>
<p><strong>第二步:下载代码仓</strong>。如果已经安装 <a href="https://git-scm.com/downloads">Git</a> ,可以通过命令行来克隆代码仓</p>
<p><strong>第一步:安装本地编程环境</strong>参照<a href="https://www.hello-algo.com/chapter_appendix/installation/">附录教程</a>进行安装,如果已安装则可跳过此步骤</p>
<p><strong>第二步:下载代码仓</strong>。如果已经安装 <a href="https://git-scm.com/downloads">Git</a> ,可以通过以下命令克隆本仓库</p>
<div class="highlight"><pre><span></span><code><a id="__codelineno-10-1" name="__codelineno-10-1" href="#__codelineno-10-1"></a>git<span class="w"> </span>clone<span class="w"> </span>https://github.com/krahets/hello-algo.git
</code></pre></div>
<p>当然你也可以点击“Download ZIP”直接下载代码压缩包本地解压即可。</p>
<p>当然你也可以点击“Download ZIP”直接下载代码压缩包然后在本地解压即可。</p>
<p><img alt="克隆仓库与下载代码" src="../suggestions.assets/download_code.png" /></p>
<p align="center"> Fig. 克隆仓库与下载代码 </p>
<p><strong>第三步:运行源代码</strong>代码块顶部标有文件名称,则可在仓库 <code>codes</code> 文件夹中找到应的 <strong>源代码文件</strong>。源代码文件可以帮助你省不必要的调试时间,将精力集中在学习内容</p>
<p><strong>第三步:运行源代码</strong>如果代码块顶部标有文件名称,则可在仓库 <code>codes</code> 文件夹中找到应的源代码文件。源代码文件帮助你省不必要的调试时间,让你能够专注于学习内容。</p>
<p><img alt="代码块与对应的源代码文件" src="../suggestions.assets/code_md_to_repo.png" /></p>
<p align="center"> Fig. 代码块与对应的源代码文件 </p>
<h2 id="025">0.2.5. &nbsp; 在提问讨论中共同成长<a class="headerlink" href="#025" title="Permanent link">&para;</a></h2>
<p>阅读本书时,请不要“惯着”那些弄不明白的知识点。<strong>欢迎在评论区留下你的问题</strong>小伙伴们和我都会给予解答,一般 2 日内会得到回复。</p>
<p>同时,也希望你可以多花时间逛逛评论区。一方面,可以看看大家遇到了什么问题,反过来查漏补缺,这往往可以引起更加深度的思考。另一方面,希望你可以慷慨地解答小伙伴的问题、分享自己的见解,大家互相学习进步</p>
<p>阅读本书时,请不要“惯着”那些没学明白的知识点。<strong>欢迎在评论区提出你的问题</strong>我和其他小伙伴们将竭诚为你解答,一般情况下可在两天内得到回复。</p>
<p>同时,也希望您能在评论区多花时间。一方面,可以了解大家遇到的问题,从而查漏补缺,这将有助于激发更深入的思考。另一方面,希望您能慷慨地回答其他小伙伴的问题、分享的见解,大家共同学习进步</p>
<p><img alt="评论区示例" src="../suggestions.assets/comment.gif" /></p>
<p align="center"> Fig. 评论区示例 </p>

View File

@ -1681,12 +1681,12 @@
<h1 id="03">0.3. &nbsp; 小结<a class="headerlink" href="#03" title="Permanent link">&para;</a></h1>
<ul>
<li>本书主要面向算法初学者。对于已经有一定积累的同学,本书可以帮助系统回顾算法知识,源代码可被当作“刷题工具库”使用。</li>
<li>书中内容主要分为复杂度分析、数据结构、算法三部分,盖了该领域的大部分主题。</li>
<li>对于算法小白,在初学阶段阅读一本入门书是非常有必要的,可以少走许多弯路。</li>
<li>书内的动画和图解往往介绍的是重点和难点知识,阅读时应该多加关注。</li>
<li>实践学习编程最佳方式,强烈推荐运行源代码,动手敲代码。</li>
<li>本书提供了讨论区,遇到疑惑可以随时提问</li>
<li>本书主要受众是算法初学者。对于已具备一定积累的同学,本书帮助系统回顾算法知识,同时源代码可作“刷题工具库”使用。</li>
<li>书中内容主要包括复杂度分析、数据结构、算法三部分,盖了该领域的大部分主题。</li>
<li>对于算法新手,在初学阶段阅读一本入门书籍至关重要,有助于避免走弯路。</li>
<li>书内的动画和图解通常用于介绍重点和难点知识,阅读时应给予更多关注。</li>
<li>实践学习编程最佳途径,强烈建议运行源代码并亲自敲打代码。</li>
<li>本书设有讨论区,欢迎随时分享你的疑惑</li>
</ul>

View File

@ -1672,7 +1672,7 @@
<p>[2] Aditya Bhargava. Grokking Algorithms: An Illustrated Guide for Programmers and Other Curious People (1<sup>st</sup> Edition).</p>
<p>[3] 严蔚敏. 数据结构C 语言版).</p>
<p>[4] 邓俊辉. 数据结构C++ 语言版,第三版).</p>
<p>[5] 马克·艾伦·维斯著,陈越译. 数据结构与算法分析Java语言描述第三版.</p>
<p>[5] 马克 艾伦 维斯著,陈越译. 数据结构与算法分析Java语言描述第三版.</p>
<p>[6] 程杰. 大话数据结构.</p>
<p>[7] 王争. 数据结构与算法之美.</p>
<p>[8] Gayle Laakmann McDowell. Cracking the Coding Interview: 189 Programming Questions and Solutions (6<sup>th</sup> Edition). </p>

View File

@ -1368,7 +1368,7 @@
<li class="md-nav__item">
<a href="#1023" class="md-nav__link">
10.2.3. &nbsp; 优点与缺点
10.2.3. &nbsp; 优点与局限性
</a>
</li>
@ -1793,7 +1793,7 @@
<li class="md-nav__item">
<a href="#1023" class="md-nav__link">
10.2.3. &nbsp; 优点与缺点
10.2.3. &nbsp; 优点与局限性
</a>
</li>
@ -1822,24 +1822,19 @@
<h1 id="102">10.2. &nbsp; 二分查找<a class="headerlink" href="#102" title="Permanent link">&para;</a></h1>
<p>「二分查找 Binary Search」利用数据的有序性通过每轮缩小一半搜索区间来查找目标元素。</p>
<p>使用二分查找有两个前置条件:</p>
<ul>
<li><strong>要求输入数据是有序的</strong>,这样才能通过判断大小关系来排除一半的搜索区间;</li>
<li><strong>二分查找仅适用于数组</strong>,而在链表中使用效率很低,因为其在循环中需要跳跃式(非连续地)访问元素。</li>
</ul>
<p>「二分查找 Binary Search」利用数据的有序性通过每轮减少一半搜索范围来定位目标元素。</p>
<h2 id="1021">10.2.1. &nbsp; 算法实现<a class="headerlink" href="#1021" title="Permanent link">&para;</a></h2>
<p>给定一个长度为 <span class="arithmatex">\(n\)</span>序数组 <code>nums</code> ,元素从小到大排列。数组索引取值范围为</p>
<p>给定一个长度为 <span class="arithmatex">\(n\)</span>序数组 <code>nums</code> ,元素从小到大的顺序排列。数组索引取值范围为</p>
<div class="arithmatex">\[
0, 1, 2, \cdots, n-1
\]</div>
<p>使用「区间」来表示这个取值范围的方法主要有两种</p>
<p>我们通常使用以下两种方法来表示这个取值范围:</p>
<ol>
<li><strong>双闭区间 <span class="arithmatex">\([0, n-1]\)</span></strong> ,即两个边界都包含自身;此方法下,区间 <span class="arithmatex">\([0, 0]\)</span> 仍包含个元素;</li>
<li><strong>左闭右开 <span class="arithmatex">\([0, n)\)</span></strong> ,即左边界包含自身、右边界不包含自身;此方法下,区间 <span class="arithmatex">\([0, 0)\)</span> 为空</li>
<li><strong>双闭区间 <span class="arithmatex">\([0, n-1]\)</span></strong> ,即两个边界都包含自身;此方法下,区间 <span class="arithmatex">\([0, 0]\)</span> 仍包含 <span class="arithmatex">\(1\)</span> 个元素;</li>
<li><strong>左闭右开 <span class="arithmatex">\([0, n)\)</span></strong> ,即左边界包含自身、右边界不包含自身;此方法下,区间 <span class="arithmatex">\([0, 0)\)</span> 不包含元素</li>
</ol>
<h3 id="_1">“双闭区间”实现<a class="headerlink" href="#_1" title="Permanent link">&para;</a></h3>
<p>首先,我们采用“双闭区间”表示,在数组 <code>nums</code> 中查找目标元素 <code>target</code> 的对应索引。</p>
<p>首先,我们采用“双闭区间”表示,在数组 <code>nums</code> 中查找目标元素 <code>target</code> 的对应索引。</p>
<div class="tabbed-set tabbed-alternate" data-tabs="1:7"><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" /><div class="tabbed-labels"><label for="__tabbed_1_1">&lt;1&gt;</label><label for="__tabbed_1_2">&lt;2&gt;</label><label for="__tabbed_1_3">&lt;3&gt;</label><label for="__tabbed_1_4">&lt;4&gt;</label><label for="__tabbed_1_5">&lt;5&gt;</label><label for="__tabbed_1_6">&lt;6&gt;</label><label for="__tabbed_1_7">&lt;7&gt;</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
@ -1865,7 +1860,7 @@
</div>
</div>
</div>
<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-content">
<div class="tabbed-block">
@ -2058,7 +2053,7 @@
</div>
</div>
<h3 id="_2">“左闭右开”实现<a class="headerlink" href="#_2" title="Permanent link">&para;</a></h3>
<p>当然,我们也可以使用“左闭右开”的表示法,写出相同功能的二分查找代码。</p>
<p>此外,我们也可以用“左闭右开”的表示法,编写具有相同功能的二分查找代码。</p>
<div class="tabbed-set tabbed-alternate" data-tabs="3:10"><input checked="checked" id="__tabbed_3_1" name="__tabbed_3" type="radio" /><input id="__tabbed_3_2" name="__tabbed_3" type="radio" /><input id="__tabbed_3_3" name="__tabbed_3" type="radio" /><input id="__tabbed_3_4" name="__tabbed_3" type="radio" /><input id="__tabbed_3_5" name="__tabbed_3" type="radio" /><input id="__tabbed_3_6" name="__tabbed_3" type="radio" /><input id="__tabbed_3_7" name="__tabbed_3" type="radio" /><input id="__tabbed_3_8" name="__tabbed_3" type="radio" /><input id="__tabbed_3_9" name="__tabbed_3" type="radio" /><input id="__tabbed_3_10" name="__tabbed_3" type="radio" /><div class="tabbed-labels"><label for="__tabbed_3_1">Java</label><label for="__tabbed_3_2">C++</label><label for="__tabbed_3_3">Python</label><label for="__tabbed_3_4">Go</label><label for="__tabbed_3_5">JavaScript</label><label for="__tabbed_3_6">TypeScript</label><label for="__tabbed_3_7">C</label><label for="__tabbed_3_8">C#</label><label for="__tabbed_3_9">Swift</label><label for="__tabbed_3_10">Zig</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
@ -2252,7 +2247,7 @@
</div>
</div>
<h3 id="_3">两种表示对比<a class="headerlink" href="#_3" title="Permanent link">&para;</a></h3>
<p>对比下来,两种表示的代码写法有以下不同点:</p>
<p>对比这两种代码写法,我们可以发现以下不同点:</p>
<div class="center-table">
<table>
<thead>
@ -2279,9 +2274,9 @@
</tbody>
</table>
</div>
<p>观察发现,在“双闭区间”表示中,由于对左右两边界的定义相同,因此缩小区间的 <span class="arithmatex">\(i\)</span> , <span class="arithmatex">\(j\)</span> 处理方法也是对称的,这样更不容易出错。综上所述<strong>建议采用“双闭区间”的写法</strong></p>
<p>在“双闭区间”表示中,由于对左右两边界的定义相同,因此缩小区间的 <span class="arithmatex">\(i\)</span> <span class="arithmatex">\(j\)</span> 处理方法也是对称的,这样更不容易出错。因此<strong>建议采用“双闭区间”的写法</strong></p>
<h3 id="_4">大数越界处理<a class="headerlink" href="#_4" title="Permanent link">&para;</a></h3>
<p>当数组长度大时,加法 <span class="arithmatex">\(i + j\)</span> 的结果可能会超出 <code>int</code> 类型的取值范围。在情况下,我们需要换一种计算中点的法。</p>
<p>当数组长度非常大时,加法 <span class="arithmatex">\(i + j\)</span> 的结果可能会超出 <code>int</code> 类型的取值范围。在这种情况下,我们需要采用一种更安全的计算中点的法。</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-content">
<div class="tabbed-block">
@ -2349,19 +2344,19 @@
</div>
</div>
<h2 id="1022">10.2.2. &nbsp; 复杂度分析<a class="headerlink" href="#1022" title="Permanent link">&para;</a></h2>
<p><strong>时间复杂度 <span class="arithmatex">\(O(\log n)\)</span></strong> :其中 <span class="arithmatex">\(n\)</span> 为数组或链表长度;每轮排除一半的区间,因此循环轮数为 <span class="arithmatex">\(\log_2 n\)</span> ,使用 <span class="arithmatex">\(O(\log n)\)</span> 时间。</p>
<p><strong>时间复杂度 <span class="arithmatex">\(O(\log n)\)</span></strong> :其中 <span class="arithmatex">\(n\)</span> 为数组长度;每轮排除一半的区间,因此循环轮数为 <span class="arithmatex">\(\log_2 n\)</span> ,使用 <span class="arithmatex">\(O(\log n)\)</span> 时间。</p>
<p><strong>空间复杂度 <span class="arithmatex">\(O(1)\)</span></strong> :指针 <code>i</code> , <code>j</code> 使用常数大小空间。</p>
<h2 id="1023">10.2.3. &nbsp; 优点与缺点<a class="headerlink" href="#1023" title="Permanent link">&para;</a></h2>
<p>二分查找效率很高,体现在:</p>
<h2 id="1023">10.2.3. &nbsp; 优点与局限性<a class="headerlink" href="#1023" title="Permanent link">&para;</a></h2>
<p>二分查找效率很高,主要体现在:</p>
<ul>
<li><strong>二分查找时间复杂度低</strong>。对数阶在数据量很大时具有巨大优势例如,当数据大小 <span class="arithmatex">\(n = 2^{20}\)</span> 时,线性查找需要 <span class="arithmatex">\(2^{20} = 1048576\)</span> 轮循环,而二分查找仅需 <span class="arithmatex">\(\log_2 2^{20} = 20\)</span> 轮循环。</li>
<li><strong>二分查找不需要额外空间</strong>相对于借助额外数据结构来实现查找的算法来说,其更加节空间使用</li>
<li><strong>二分查找时间复杂度</strong>。对数阶在数据量情况下具有显著优势例如,当数据大小 <span class="arithmatex">\(n = 2^{20}\)</span> 时,线性查找需要 <span class="arithmatex">\(2^{20} = 1048576\)</span> 轮循环,而二分查找仅需 <span class="arithmatex">\(\log_2 2^{20} = 20\)</span> 轮循环。</li>
<li><strong>二分查找无需额外空间</strong>与哈希查找相比,二分查找更加节空间。</li>
</ul>
<p>但并不意味着所有情况下都使用二分查找,这是因为</p>
<p>然而,并非所有情况下都使用二分查找,原因如下</p>
<ul>
<li><strong>二分查找仅适用于有序数据</strong>如果输入数据无序,为了使用二分查找而专门执行数据排序,那么是得不偿失的,因为排序算法的时间复杂度一般<span class="arithmatex">\(O(n \log n)\)</span> ,比线性查找和二分查找都更差。再例如,对于频繁插入元素的场景,为保持数组有序性,需要将元素插入到特定位置,时间复杂度为 <span class="arithmatex">\(O(n)\)</span> ,也是非常昂贵的。</li>
<li><strong>二分查找仅适用于数组</strong>由于在二分查找中,访问索引是 “非连续” 的,因此链表或基于链表实现的数据结构都无法使用</li>
<li><strong>小数据量下,线性查找性能更</strong>。在线性查找中,每轮只需要 1 次判断操作;而在二分查找中,需要 1 次加法、1 次除法、1 ~ 3 次判断操作、1 次加法(减法),共 4 ~ 6 个单元操作;因此,数据量 <span class="arithmatex">\(n\)</span> 较小时,线性查找反而比二分查找更快。</li>
<li><strong>二分查找仅适用于有序数据</strong>输入数据无序,为了使用二分查找而专门进行排序,得不偿失因为排序算法的时间复杂度通常<span class="arithmatex">\(O(n \log n)\)</span> ,比线性查找和二分查找都更高。对于频繁插入元素的场景,为保持数组有序性,需要将元素插入到特定位置,时间复杂度为 <span class="arithmatex">\(O(n)\)</span> ,也是非常昂贵的。</li>
<li><strong>二分查找仅适用于数组</strong>。二分查找需要跳跃式(非连续地)访问元素,而在链表中执行跳跃式访问的效率较低,因此不适合应用在链表或基于链表实现的数据结构。</li>
<li><strong>小数据量下,线性查找性能更</strong>。在线性查找中,每轮只需要 1 次判断操作;而在二分查找中,需要 1 次加法、1 次除法、1 ~ 3 次判断操作、1 次加法(减法),共 4 ~ 6 个单元操作;因此,数据量 <span class="arithmatex">\(n\)</span> 较小时,线性查找反而比二分查找更快。</li>
</ul>

View File

@ -1348,7 +1348,7 @@
<li class="md-nav__item">
<a href="#1033" class="md-nav__link">
10.3.3. &nbsp; 优点与缺点
10.3.3. &nbsp; 优点与局限性
</a>
</li>
@ -1725,7 +1725,7 @@
<li class="md-nav__item">
<a href="#1033" class="md-nav__link">
10.3.3. &nbsp; 优点与缺点
10.3.3. &nbsp; 优点与局限性
</a>
</li>
@ -1754,13 +1754,10 @@
<h1 id="103">10.3. &nbsp; 哈希查找<a class="headerlink" href="#103" title="Permanent link">&para;</a></h1>
<div class="admonition question">
<p class="admonition-title">Question</p>
<p>在数据量很大时,「线性查找」太慢;而「二分查找」要求数据必须是有序的,并且只能在数组中应用。那么是否有方法可以同时避免上述缺点呢?答案是肯定的,此方法被称为「哈希查找」。</p>
</div>
<p>「哈希查找 Hash Searching」借助一个哈希表来存储需要的「键值对 Key Value Pair」我们可以在 <span class="arithmatex">\(O(1)\)</span> 时间下实现“键 <span class="arithmatex">\(\rightarrow\)</span> 值”映射查找,体现着“以空间换时间”的算法思想。</p>
<p>「哈希查找 Hash Searching」通过使用哈希表来存储所需的键值对从而可在 <span class="arithmatex">\(O(1)\)</span> 时间内完成“键 <span class="arithmatex">\(\rightarrow\)</span> 值”的查找操作。</p>
<p>与线性查找相比,哈希查找通过利用额外空间来提高效率,体现了“以空间换时间”的算法思想。</p>
<h2 id="1031">10.3.1. &nbsp; 算法实现<a class="headerlink" href="#1031" title="Permanent link">&para;</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 align="center"> Fig. 哈希查找数组索引 </p>
@ -1860,7 +1857,7 @@
</div>
</div>
</div>
<p>再比如,如果我们想要给定一个目标节点值 <code>target</code> ,获取对应的链表节点对象,那么也可以使用哈希查找实现</p>
<p>同样,若要根据目标节点值 target 查找对应的链表节点对象,也可以用哈希查找方法</p>
<p><img alt="哈希查找链表节点" src="../hashing_search.assets/hash_search_listnode.png" /></p>
<p align="center"> Fig. 哈希查找链表节点 </p>
@ -1960,16 +1957,16 @@
</div>
<h2 id="1032">10.3.2. &nbsp; 复杂度分析<a class="headerlink" href="#1032" title="Permanent link">&para;</a></h2>
<p><strong>时间复杂度 <span class="arithmatex">\(O(1)\)</span></strong> :哈希表的查找操作使用 <span class="arithmatex">\(O(1)\)</span> 时间。</p>
<p><strong>空间复杂度 <span class="arithmatex">\(O(n)\)</span></strong> :其中 <span class="arithmatex">\(n\)</span> 数组或链表长度。</p>
<h2 id="1033">10.3.3. &nbsp; 优点与缺点<a class="headerlink" href="#1033" title="Permanent link">&para;</a></h2>
<p>哈希表中,<strong>查找、插入、删除操作的平均时间复杂度<span class="arithmatex">\(O(1)\)</span></strong> ,这意味着无论是高频增删还是高频查找场景,哈希查找的性能表现都非常好。当然,一切的前提是保证哈希表未退化。</p>
<p>即使如此,哈希查找仍存在一些问题,在实际应用中,需要根据情况灵活选择方法。</p>
<p><strong>空间复杂度 <span class="arithmatex">\(O(n)\)</span></strong> :其中 <span class="arithmatex">\(n\)</span> 数组或链表长度。</p>
<h2 id="1033">10.3.3. &nbsp; 优点与局限性<a class="headerlink" href="#1033" title="Permanent link">&para;</a></h2>
<p>哈希查找的性能表现相当优秀,查找、插入、删除操作的平均时间复杂度<span class="arithmatex">\(O(1)\)</span> 。尽管如此,哈希查找仍然存在一些问题:</p>
<ul>
<li>辅助哈希表 <strong>需要使<span class="arithmatex">\(O(n)\)</span> 的额外空间</strong>,意味着需要预留更多的计算机内存;</li>
<li>和维护哈希表需要时间,因此哈希查找 <strong>不适合高频增删、低频查找的使用场景</strong></li>
<li>当哈希冲突严重时,哈希表退化为链表,<strong>时间复杂度劣化至 <span class="arithmatex">\(O(n)\)</span></strong> </li>
<li><strong>当数据量小时,线性查找比哈希查找更快</strong>。这是因为计算哈希映射函数可能比遍历一个小型数组更慢;</li>
<li>辅助哈希表需要<span class="arithmatex">\(O(n)\)</span> 的额外空间,意味着需要预留更多的计算机内存;</li>
<li>建和维护哈希表需要时间,因此哈希查找不适用于高频增删、低频查找的场景</li>
<li>当哈希冲突严重时,哈希表可能退化为链表,导致时间复杂度劣化至 <span class="arithmatex">\(O(n)\)</span> </li>
<li>当数据量小时,线性查找可能比哈希查找更快。这是因为计算哈希函数可能比遍历一个小型数组更慢;</li>
</ul>
<p>因此,在实际应用中,我们需要根据具体情况灵活选择解决方案。</p>

View File

@ -1320,7 +1320,7 @@
<li class="md-nav__item">
<a href="#1013" class="md-nav__link">
10.1.3. &nbsp; 优点与缺点
10.1.3. &nbsp; 优点与局限性
</a>
</li>
@ -1725,7 +1725,7 @@
<li class="md-nav__item">
<a href="#1013" class="md-nav__link">
10.1.3. &nbsp; 优点与缺点
10.1.3. &nbsp; 优点与局限性
</a>
</li>
@ -1754,9 +1754,9 @@
<h1 id="101">10.1. &nbsp; 线性查找<a class="headerlink" href="#101" title="Permanent link">&para;</a></h1>
<p>「线性查找 Linear Search」是一种最基础的查找方法,其从数据结构的一端开始,依次访问每个元素,直另一端后停止。</p>
<p>「线性查找 Linear Search」是一种简单的查找方法,其从数据结构的一端开始,逐个访问每个元素,直另一端止。</p>
<h2 id="1011">10.1.1. &nbsp; 算法实现<a class="headerlink" href="#1011" title="Permanent link">&para;</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 align="center"> Fig. 在数组中线性查找元素 </p>
@ -1897,7 +1897,7 @@
</div>
</div>
</div>
<p>再比如,我们想要在给定一个目标节点值 <code>target</code> 返回节点对象,也可以在链表中进行线性查找。</p>
<p>另一个例子,若需要在链表中查找给定目标节点值 <code>target</code> 返回节点对象,同样可以使用线性查找。</p>
<div class="tabbed-set tabbed-alternate" data-tabs="2:10"><input checked="checked" id="__tabbed_2_1" name="__tabbed_2" type="radio" /><input id="__tabbed_2_2" name="__tabbed_2" type="radio" /><input id="__tabbed_2_3" name="__tabbed_2" type="radio" /><input id="__tabbed_2_4" name="__tabbed_2" type="radio" /><input id="__tabbed_2_5" name="__tabbed_2" type="radio" /><input id="__tabbed_2_6" name="__tabbed_2" type="radio" /><input id="__tabbed_2_7" name="__tabbed_2" type="radio" /><input id="__tabbed_2_8" name="__tabbed_2" type="radio" /><input id="__tabbed_2_9" name="__tabbed_2" type="radio" /><input id="__tabbed_2_10" name="__tabbed_2" type="radio" /><div class="tabbed-labels"><label for="__tabbed_2_1">Java</label><label for="__tabbed_2_2">C++</label><label for="__tabbed_2_3">Python</label><label for="__tabbed_2_4">Go</label><label for="__tabbed_2_5">JavaScript</label><label for="__tabbed_2_6">TypeScript</label><label for="__tabbed_2_7">C</label><label for="__tabbed_2_8">C#</label><label for="__tabbed_2_9">Swift</label><label for="__tabbed_2_10">Zig</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
@ -2044,11 +2044,11 @@
</div>
</div>
<h2 id="1012">10.1.2. &nbsp; 复杂度分析<a class="headerlink" href="#1012" title="Permanent link">&para;</a></h2>
<p><strong>时间复杂度 <span class="arithmatex">\(O(n)\)</span></strong> :其中 <span class="arithmatex">\(n\)</span> 数组或链表长度。</p>
<p><strong>空间复杂度 <span class="arithmatex">\(O(1)\)</span></strong> :无需使用额外空间。</p>
<h2 id="1013">10.1.3. &nbsp; 优点与缺点<a class="headerlink" href="#1013" title="Permanent link">&para;</a></h2>
<p><strong>线性查找的通用性极佳</strong>。由于线性查找是依次访问元素的,没有跳跃访问元素,因此数组链表皆适用</p>
<p><strong>线性查找的时间复杂度</strong>数据量 <span class="arithmatex">\(n\)</span> 大时,查找效率低。</p>
<p><strong>时间复杂度 <span class="arithmatex">\(O(n)\)</span></strong> :其中 <span class="arithmatex">\(n\)</span> 代表数组或链表长度。</p>
<p><strong>空间复杂度 <span class="arithmatex">\(O(1)\)</span></strong> :无需借助额外的存储空间。</p>
<h2 id="1013">10.1.3. &nbsp; 优点与局限性<a class="headerlink" href="#1013" title="Permanent link">&para;</a></h2>
<p><strong>线性查找具有极佳的通用性</strong>。由于线性查找是逐个访问元素的,没有跳跃访问,因此适用于数组链表的查找</p>
<p><strong>线性查找的时间复杂度</strong>数据量 <span class="arithmatex">\(n\)</span> 大时,线性查找效率低。</p>

View File

@ -1681,10 +1681,10 @@
<h1 id="104">10.4. &nbsp; 小结<a class="headerlink" href="#104" title="Permanent link">&para;</a></h1>
<ul>
<li>线性查找是一种最基础的查找方法,通过遍历数据结构 + 判断条件实现查找</li>
<li>二分查找利用数据的有序性,通过循环不断缩小一半搜索区间来实现查找,其要求输入数据有序的,并且仅适用于数组或基于数组实现的数据结构。</li>
<li>哈希查找借助哈希表实现常数阶时间复杂度的查找操作,体现空间换时间的算法思</li>
<li>下表总结对比了查找算法的各种特性和时间复杂度。</li>
<li>线性查找通过遍历数据结构并进行条件判断来完成查找任务</li>
<li>二分查找依赖于数据的有序性,通过循环逐步缩减一半搜索区间来实现查找。它要求输入数据有序且仅适用于数组或基于数组实现的数据结构。</li>
<li>哈希查找利用哈希表实现常数阶时间复杂度的查找操作,体现空间换时间的算法思</li>
<li>下表概括并对比了三种查找算法的特性和时间复杂度。</li>
</ul>
<div class="center-table">
<table>
@ -1700,26 +1700,14 @@
<tr>
<td>适用数据结构</td>
<td>数组、链表</td>
<td>数组</td>
<td>有序数组</td>
<td>数组、链表</td>
</tr>
<tr>
<td>输入数据要求</td>
<td></td>
<td>有序</td>
<td></td>
</tr>
<tr>
<td>平均时间复杂度</br>查找 / 插入 / 删除</td>
<td><span class="arithmatex">\(O(n)\)</span> / <span class="arithmatex">\(O(1)\)</span> / <span class="arithmatex">\(O(n)\)</span></td>
<td><span class="arithmatex">\(O(\log n)\)</span> / <span class="arithmatex">\(O(n)\)</span> / <span class="arithmatex">\(O(n)\)</span></td>
<td><span class="arithmatex">\(O(1)\)</span> / <span class="arithmatex">\(O(1)\)</span> / <span class="arithmatex">\(O(1)\)</span></td>
</tr>
<tr>
<td>最差时间复杂度</br>查找 / 插入 / 删除</td>
<td><span class="arithmatex">\(O(n)\)</span> / <span class="arithmatex">\(O(1)\)</span> / <span class="arithmatex">\(O(n)\)</span></td>
<td><span class="arithmatex">\(O(\log n)\)</span> / <span class="arithmatex">\(O(n)\)</span> / <span class="arithmatex">\(O(n)\)</span></td>
<td><span class="arithmatex">\(O(n)\)</span> / <span class="arithmatex">\(O(n)\)</span> / <span class="arithmatex">\(O(n)\)</span></td>
<td>时间复杂度</br>(查找,插入,删除)</td>
<td><span class="arithmatex">\(O(n)\)</span> , <span class="arithmatex">\(O(1)\)</span> , <span class="arithmatex">\(O(n)\)</span></td>
<td><span class="arithmatex">\(O(\log n)\)</span> , <span class="arithmatex">\(O(n)\)</span> , <span class="arithmatex">\(O(n)\)</span></td>
<td><span class="arithmatex">\(O(1)\)</span> , <span class="arithmatex">\(O(1)\)</span> , <span class="arithmatex">\(O(1)\)</span></td>
</tr>
<tr>
<td>空间复杂度</td>

View File

@ -1754,13 +1754,9 @@
<h1 id="112">11.2. &nbsp; 冒泡排序<a class="headerlink" href="#112" title="Permanent link">&para;</a></h1>
<p>「冒泡排序 Bubble Sort」是一种基于元素交换实现排序的算法,非常适合作为第一个学习的排序算法</p>
<div class="admonition question">
<p class="admonition-title">为什么叫“冒泡”</p>
<p>在水中,越大的泡泡浮力越大,所以最大的泡泡会最先浮到水面。</p>
</div>
<p>「冒泡操作」则是在模拟上述过程,具体做法为:从数组最左端开始向右遍历,依次对比相邻元素大小,若“左元素 &gt; 右元素”则将它俩交换,最终可将最大元素移动至数组最右端。</p>
<p>完成一次冒泡操作后,<strong>数组最大元素已在正确位置,接下来只需排序剩余 <span class="arithmatex">\(n - 1\)</span> 个元素</strong></p>
<p>「冒泡排序 Bubble Sort」的工作原理类似于泡泡在水中的浮动。在水中,较大的泡泡会最先浮到水面</p>
<p>「冒泡操作」利用元素交换操作模拟了上述过程,具体做法为:从数组最左端开始向右遍历,依次比较相邻元素大小,如果“左元素 &gt; 右元素”就交换它俩。遍历完成后,最大的元素会被移动到数组的最右端。</p>
<p><strong>在完成一次冒泡操作后,数组的最大元素已位于正确位置,接下来只需对剩余 <span class="arithmatex">\(n - 1\)</span> 个元素进行排序</strong></p>
<div class="tabbed-set tabbed-alternate" data-tabs="1:7"><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" /><div class="tabbed-labels"><label for="__tabbed_1_1">&lt;1&gt;</label><label for="__tabbed_1_2">&lt;2&gt;</label><label for="__tabbed_1_3">&lt;3&gt;</label><label for="__tabbed_1_4">&lt;4&gt;</label><label for="__tabbed_1_5">&lt;5&gt;</label><label for="__tabbed_1_6">&lt;6&gt;</label><label for="__tabbed_1_7">&lt;7&gt;</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
@ -1787,11 +1783,11 @@
</div>
</div>
<h2 id="1121">11.2.1. &nbsp; 算法流程<a class="headerlink" href="#1121" title="Permanent link">&para;</a></h2>
<p>设输入数组长度为 <span class="arithmatex">\(n\)</span> 循环执行「冒泡」操作</p>
<p>设输入数组长度为 <span class="arithmatex">\(n\)</span> 整个冒泡排序的步骤为</p>
<ol>
<li>完成第一轮「冒泡」后,数组最大元素已正确位置,接下来只需排序剩余 <span class="arithmatex">\(n - 1\)</span> 个元素;</li>
<li>对剩余 <span class="arithmatex">\(n - 1\)</span> 个元素执行冒泡,可将第二大元素交换至正确位置,因而待排序元素只剩 <span class="arithmatex">\(n - 2\)</span> 个;</li>
<li>此类推…… <strong>循环 <span class="arithmatex">\(n - 1\)</span>冒泡」,即可完成整个数组的排序</strong></li>
<li>完成第一轮「冒泡」后,数组最大元素已位于正确位置,接下来只需剩余 <span class="arithmatex">\(n - 1\)</span> 个元素进行排序</li>
<li>对剩余 <span class="arithmatex">\(n - 1\)</span> 个元素执行冒泡操作,可将第二大元素交换至正确位置,因而待排序元素只剩 <span class="arithmatex">\(n - 2\)</span> 个;</li>
<li>此类推,经过 <span class="arithmatex">\(n - 1\)</span> 轮冒泡操作,整个数组便完成排序</li>
</ol>
<p><img alt="冒泡排序流程" src="../bubble_sort.assets/bubble_sort_overview.png" /></p>
<p align="center"> Fig. 冒泡排序流程 </p>
@ -1965,12 +1961,12 @@
</div>
</div>
<h2 id="1122">11.2.2. &nbsp; 算法特性<a class="headerlink" href="#1122" title="Permanent link">&para;</a></h2>
<p><strong>时间复杂度 <span class="arithmatex">\(O(n^2)\)</span></strong> :各轮冒泡遍历的数组长度为 <span class="arithmatex">\(n - 1\)</span> , <span class="arithmatex">\(n - 2\)</span> , <span class="arithmatex">\(\cdots\)</span> , <span class="arithmatex">\(2\)</span> , <span class="arithmatex">\(1\)</span> 次,求和为 <span class="arithmatex">\(\frac{(n - 1) n}{2}\)</span> ,因此使用 <span class="arithmatex">\(O(n^2)\)</span> 时间。引入下文的 <code>flag</code> 优化后,最佳时间复杂度可达到 <span class="arithmatex">\(O(N)\)</span> 因此是“自适应排序”。</p>
<p><strong>时间复杂度 <span class="arithmatex">\(O(n^2)\)</span></strong> :各轮冒泡遍历的数组长度依次<span class="arithmatex">\(n - 1\)</span> , <span class="arithmatex">\(n - 2\)</span> , <span class="arithmatex">\(\cdots\)</span> , <span class="arithmatex">\(2\)</span> , <span class="arithmatex">\(1\)</span> ,总和为 <span class="arithmatex">\(\frac{(n - 1) n}{2}\)</span> ,因此使用 <span class="arithmatex">\(O(n^2)\)</span> 时间。引入下文的 <code>flag</code> 优化后,最佳时间复杂度可达到 <span class="arithmatex">\(O(n)\)</span> 所以它是“自适应排序”。</p>
<p><strong>空间复杂度 <span class="arithmatex">\(O(1)\)</span></strong> :指针 <span class="arithmatex">\(i\)</span> , <span class="arithmatex">\(j\)</span> 使用常数大小的额外空间,因此是“原地排序”。</p>
<p>冒泡操作中遇到相等元素不交换,因此是“稳定排序”。</p>
<p>由于冒泡操作中遇到相等元素不交换,因此冒泡排序是“稳定排序”。</p>
<h2 id="1123">11.2.3. &nbsp; 效率优化<a class="headerlink" href="#1123" title="Permanent link">&para;</a></h2>
<p>我们发现,若在某轮冒泡」中未执行任何交换操作,说明数组已经完成排序,可直接返回结果。考虑可以增加一个标志位 <code>flag</code> 来监听该情况,若出现则直接返回。</p>
<p>优化,冒泡排序的最差和平均时间复杂度仍为 <span class="arithmatex">\(O(n^2)\)</span> 而在输入数组完全有序时,达到最佳时间复杂度 <span class="arithmatex">\(O(n)\)</span> </p>
<p>我们发现,如果某轮冒泡操作中没有执行任何交换操作,说明数组已经完成排序,可直接返回结果。因此,可以增加一个标志位 <code>flag</code> 来监测这种情况,一旦出现就立即返回。</p>
<p>经过优化,冒泡排序的最差和平均时间复杂度仍为 <span class="arithmatex">\(O(n^2)\)</span> 但当输入数组完全有序时,达到最佳时间复杂度 <span class="arithmatex">\(O(n)\)</span></p>
<div class="tabbed-set tabbed-alternate" data-tabs="3:10"><input checked="checked" id="__tabbed_3_1" name="__tabbed_3" type="radio" /><input id="__tabbed_3_2" name="__tabbed_3" type="radio" /><input id="__tabbed_3_3" name="__tabbed_3" type="radio" /><input id="__tabbed_3_4" name="__tabbed_3" type="radio" /><input id="__tabbed_3_5" name="__tabbed_3" type="radio" /><input id="__tabbed_3_6" name="__tabbed_3" type="radio" /><input id="__tabbed_3_7" name="__tabbed_3" type="radio" /><input id="__tabbed_3_8" name="__tabbed_3" type="radio" /><input id="__tabbed_3_9" name="__tabbed_3" type="radio" /><input id="__tabbed_3_10" name="__tabbed_3" type="radio" /><div class="tabbed-labels"><label for="__tabbed_3_1">Java</label><label for="__tabbed_3_2">C++</label><label for="__tabbed_3_3">Python</label><label for="__tabbed_3_4">Go</label><label for="__tabbed_3_5">JavaScript</label><label for="__tabbed_3_6">TypeScript</label><label for="__tabbed_3_7">C</label><label for="__tabbed_3_8">C#</label><label for="__tabbed_3_9">Swift</label><label for="__tabbed_3_10">Zig</label></div>
<div class="tabbed-content">
<div class="tabbed-block">

View File

@ -1754,12 +1754,12 @@
<h1 id="116">11.6. &nbsp; 桶排序<a class="headerlink" href="#116" title="Permanent link">&para;</a></h1>
<p>面介绍的几种排序算法都属于 <strong>基于比较的排序算法</strong>,即通过比较元素间的大小来实现排序此类排序算法的时间复杂度无法超越 <span class="arithmatex">\(O(n \log n)\)</span> 。接下来,我们将学习几种 <strong>非比较排序算法</strong> ,其时间复杂度可以达到线性级别</p>
<p>「桶排序 Bucket Sort」是分治思想的典型体现,其通过设置一些具有大小顺序的桶,每个桶对应一个数据范围,将数据平均分配到各个桶中,在每个桶内部分别执行排序最终按照桶的顺序将所有数据合并即可</p>
<p>的几种排序算法都属于“基于比较的排序算法”,它们通过比较元素间的大小来实现排序此类排序算法的时间复杂度无法超越 <span class="arithmatex">\(O(n \log n)\)</span> 。接下来,我们将探讨几种“非比较排序算法”,它们的时间复杂度可以达到线性水平</p>
<p>「桶排序 Bucket Sort」是分治思想的一个典型应用。它通过设置一些具有大小顺序的桶,每个桶对应一个数据范围,将数据平均分配到各个桶中;然后,在每个桶内部分别执行排序最终按照桶的顺序将所有数据合并。</p>
<h2 id="1161">11.6.1. &nbsp; 算法流程<a class="headerlink" href="#1161" title="Permanent link">&para;</a></h2>
<p>输入一个长度为 <span class="arithmatex">\(n\)</span> 的数组,元素是范围 <span class="arithmatex">\([0, 1)\)</span> 的浮点数桶排序流程</p>
<p>考虑一个长度为 <span class="arithmatex">\(n\)</span> 的数组,元素是范围 <span class="arithmatex">\([0, 1)\)</span> 的浮点数桶排序流程如下</p>
<ol>
<li>初始化 <span class="arithmatex">\(k\)</span> 个桶,将 <span class="arithmatex">\(n\)</span> 个元素分配 <span class="arithmatex">\(k\)</span> 个桶中;</li>
<li>初始化 <span class="arithmatex">\(k\)</span> 个桶,将 <span class="arithmatex">\(n\)</span> 个元素分配 <span class="arithmatex">\(k\)</span> 个桶中;</li>
<li>对每个桶分别执行排序(本文采用编程语言的内置排序函数);</li>
<li>按照桶的从小到大的顺序,合并结果;</li>
</ol>
@ -1786,7 +1786,7 @@
<a id="__codelineno-0-15" name="__codelineno-0-15" href="#__codelineno-0-15"></a><span class="w"> </span><span class="p">}</span>
<a id="__codelineno-0-16" name="__codelineno-0-16" href="#__codelineno-0-16"></a><span class="w"> </span><span class="c1">// 2. 对各个桶执行排序</span>
<a id="__codelineno-0-17" name="__codelineno-0-17" href="#__codelineno-0-17"></a><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="n">List</span><span class="o">&lt;</span><span class="n">Float</span><span class="o">&gt;</span><span class="w"> </span><span class="n">bucket</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="n">buckets</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<a id="__codelineno-0-18" name="__codelineno-0-18" href="#__codelineno-0-18"></a><span class="w"> </span><span class="c1">// 使用内置排序函数,也可以替换成其排序算法</span>
<a id="__codelineno-0-18" name="__codelineno-0-18" href="#__codelineno-0-18"></a><span class="w"> </span><span class="c1">// 使用内置排序函数,也可以替换成其排序算法</span>
<a id="__codelineno-0-19" name="__codelineno-0-19" href="#__codelineno-0-19"></a><span class="w"> </span><span class="n">Collections</span><span class="p">.</span><span class="na">sort</span><span class="p">(</span><span class="n">bucket</span><span class="p">);</span>
<a id="__codelineno-0-20" name="__codelineno-0-20" href="#__codelineno-0-20"></a><span class="w"> </span><span class="p">}</span>
<a id="__codelineno-0-21" name="__codelineno-0-21" href="#__codelineno-0-21"></a><span class="w"> </span><span class="c1">// 3. 遍历桶合并结果</span>
@ -1800,31 +1800,7 @@
</code></pre></div>
</div>
<div class="tabbed-block">
<div class="highlight"><span class="filename">bucket_sort.cpp</span><pre><span></span><code><a id="__codelineno-1-1" name="__codelineno-1-1" href="#__codelineno-1-1"></a><span class="cm">/* 桶排序 */</span>
<a id="__codelineno-1-2" name="__codelineno-1-2" href="#__codelineno-1-2"></a><span class="kt">void</span><span class="w"> </span><span class="nf">bucketSort</span><span class="p">(</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span><span class="w"> </span><span class="o">&amp;</span><span class="n">nums</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<a id="__codelineno-1-3" name="__codelineno-1-3" href="#__codelineno-1-3"></a><span class="w"> </span><span class="c1">// 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素</span>
<a id="__codelineno-1-4" name="__codelineno-1-4" href="#__codelineno-1-4"></a><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">k</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">nums</span><span class="p">.</span><span class="n">size</span><span class="p">()</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mi">2</span><span class="p">;</span>
<a id="__codelineno-1-5" name="__codelineno-1-5" href="#__codelineno-1-5"></a><span class="w"> </span><span class="n">vector</span><span class="o">&lt;</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;&gt;</span><span class="w"> </span><span class="n">buckets</span><span class="p">(</span><span class="n">k</span><span class="p">);</span>
<a id="__codelineno-1-6" name="__codelineno-1-6" href="#__codelineno-1-6"></a><span class="w"> </span><span class="c1">// 1. 将数组元素分配到各个桶中</span>
<a id="__codelineno-1-7" name="__codelineno-1-7" href="#__codelineno-1-7"></a><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="kt">float</span><span class="w"> </span><span class="n">num</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="n">nums</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<a id="__codelineno-1-8" name="__codelineno-1-8" href="#__codelineno-1-8"></a><span class="w"> </span><span class="c1">// 输入数据范围 [0, 1),使用 num * k 映射到索引范围 [0, k-1]</span>
<a id="__codelineno-1-9" name="__codelineno-1-9" href="#__codelineno-1-9"></a><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">num</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">k</span><span class="p">;</span>
<a id="__codelineno-1-10" name="__codelineno-1-10" href="#__codelineno-1-10"></a><span class="w"> </span><span class="c1">// 将 num 添加进桶 bucket_idx</span>
<a id="__codelineno-1-11" name="__codelineno-1-11" href="#__codelineno-1-11"></a><span class="w"> </span><span class="n">buckets</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">push_back</span><span class="p">(</span><span class="n">num</span><span class="p">);</span>
<a id="__codelineno-1-12" name="__codelineno-1-12" href="#__codelineno-1-12"></a><span class="w"> </span><span class="p">}</span>
<a id="__codelineno-1-13" name="__codelineno-1-13" href="#__codelineno-1-13"></a><span class="w"> </span><span class="c1">// 2. 对各个桶执行排序</span>
<a id="__codelineno-1-14" name="__codelineno-1-14" href="#__codelineno-1-14"></a><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span><span class="w"> </span><span class="o">&amp;</span><span class="n">bucket</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="n">buckets</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<a id="__codelineno-1-15" name="__codelineno-1-15" href="#__codelineno-1-15"></a><span class="w"> </span><span class="c1">// 使用内置排序函数,也可以替换成其它排序算法</span>
<a id="__codelineno-1-16" name="__codelineno-1-16" href="#__codelineno-1-16"></a><span class="w"> </span><span class="n">sort</span><span class="p">(</span><span class="n">bucket</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span><span class="w"> </span><span class="n">bucket</span><span class="p">.</span><span class="n">end</span><span class="p">());</span>
<a id="__codelineno-1-17" name="__codelineno-1-17" href="#__codelineno-1-17"></a><span class="w"> </span><span class="p">}</span>
<a id="__codelineno-1-18" name="__codelineno-1-18" href="#__codelineno-1-18"></a><span class="w"> </span><span class="c1">// 3. 遍历桶合并结果</span>
<a id="__codelineno-1-19" name="__codelineno-1-19" href="#__codelineno-1-19"></a><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span>
<a id="__codelineno-1-20" name="__codelineno-1-20" href="#__codelineno-1-20"></a><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span><span class="w"> </span><span class="o">&amp;</span><span class="n">bucket</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="n">buckets</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<a id="__codelineno-1-21" name="__codelineno-1-21" href="#__codelineno-1-21"></a><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="kt">float</span><span class="w"> </span><span class="n">num</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="n">bucket</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<a id="__codelineno-1-22" name="__codelineno-1-22" href="#__codelineno-1-22"></a><span class="w"> </span><span class="n">nums</span><span class="p">[</span><span class="n">i</span><span class="o">++</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">num</span><span class="p">;</span>
<a id="__codelineno-1-23" name="__codelineno-1-23" href="#__codelineno-1-23"></a><span class="w"> </span><span class="p">}</span>
<a id="__codelineno-1-24" name="__codelineno-1-24" href="#__codelineno-1-24"></a><span class="w"> </span><span class="p">}</span>
<a id="__codelineno-1-25" name="__codelineno-1-25" href="#__codelineno-1-25"></a><span class="p">}</span>
<div class="highlight"><span class="filename">bucket_sort.cpp</span><pre><span></span><code><a id="__codelineno-1-1" name="__codelineno-1-1" href="#__codelineno-1-1"></a><span class="p">[</span><span class="k">class</span><span class="p">]{}</span><span class="o">-</span><span class="p">[</span><span class="n">func</span><span class="p">]{</span><span class="n">bucketSort</span><span class="p">}</span>
</code></pre></div>
</div>
<div class="tabbed-block">
@ -1841,7 +1817,7 @@
<a id="__codelineno-2-11" name="__codelineno-2-11" href="#__codelineno-2-11"></a> <span class="n">buckets</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">num</span><span class="p">)</span>
<a id="__codelineno-2-12" name="__codelineno-2-12" href="#__codelineno-2-12"></a> <span class="c1"># 2. 对各个桶执行排序5</span>
<a id="__codelineno-2-13" name="__codelineno-2-13" href="#__codelineno-2-13"></a> <span class="k">for</span> <span class="n">bucket</span> <span class="ow">in</span> <span class="n">buckets</span><span class="p">:</span>
<a id="__codelineno-2-14" name="__codelineno-2-14" href="#__codelineno-2-14"></a> <span class="c1"># 使用内置排序函数,也可以替换成其排序算法</span>
<a id="__codelineno-2-14" name="__codelineno-2-14" href="#__codelineno-2-14"></a> <span class="c1"># 使用内置排序函数,也可以替换成其排序算法</span>
<a id="__codelineno-2-15" name="__codelineno-2-15" href="#__codelineno-2-15"></a> <span class="n">bucket</span><span class="o">.</span><span class="n">sort</span><span class="p">()</span>
<a id="__codelineno-2-16" name="__codelineno-2-16" href="#__codelineno-2-16"></a> <span class="c1"># 3. 遍历桶合并结果</span>
<a id="__codelineno-2-17" name="__codelineno-2-17" href="#__codelineno-2-17"></a> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span>
@ -1869,7 +1845,7 @@
<a id="__codelineno-3-15" name="__codelineno-3-15" href="#__codelineno-3-15"></a><span class="w"> </span><span class="p">}</span>
<a id="__codelineno-3-16" name="__codelineno-3-16" href="#__codelineno-3-16"></a><span class="w"> </span><span class="c1">// 2. 对各个桶执行排序</span>
<a id="__codelineno-3-17" name="__codelineno-3-17" href="#__codelineno-3-17"></a><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="nx">k</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span>
<a id="__codelineno-3-18" name="__codelineno-3-18" href="#__codelineno-3-18"></a><span class="w"> </span><span class="c1">// 使用内置切片排序函数,也可以替换成其排序算法</span>
<a id="__codelineno-3-18" name="__codelineno-3-18" href="#__codelineno-3-18"></a><span class="w"> </span><span class="c1">// 使用内置切片排序函数,也可以替换成其排序算法</span>
<a id="__codelineno-3-19" name="__codelineno-3-19" href="#__codelineno-3-19"></a><span class="w"> </span><span class="nx">sort</span><span class="p">.</span><span class="nx">Float64s</span><span class="p">(</span><span class="nx">buckets</span><span class="p">[</span><span class="nx">i</span><span class="p">])</span>
<a id="__codelineno-3-20" name="__codelineno-3-20" href="#__codelineno-3-20"></a><span class="w"> </span><span class="p">}</span>
<a id="__codelineno-3-21" name="__codelineno-3-21" href="#__codelineno-3-21"></a><span class="w"> </span><span class="c1">// 3. 遍历桶合并结果</span>
@ -1901,7 +1877,7 @@
<a id="__codelineno-4-15" name="__codelineno-4-15" href="#__codelineno-4-15"></a><span class="w"> </span><span class="p">}</span>
<a id="__codelineno-4-16" name="__codelineno-4-16" href="#__codelineno-4-16"></a><span class="w"> </span><span class="c1">// 2. 对各个桶执行排序</span>
<a id="__codelineno-4-17" name="__codelineno-4-17" href="#__codelineno-4-17"></a><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="kd">const</span><span class="w"> </span><span class="nx">bucket</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="nx">buckets</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<a id="__codelineno-4-18" name="__codelineno-4-18" href="#__codelineno-4-18"></a><span class="w"> </span><span class="c1">// 使用内置排序函数,也可以替换成其排序算法</span>
<a id="__codelineno-4-18" name="__codelineno-4-18" href="#__codelineno-4-18"></a><span class="w"> </span><span class="c1">// 使用内置排序函数,也可以替换成其排序算法</span>
<a id="__codelineno-4-19" name="__codelineno-4-19" href="#__codelineno-4-19"></a><span class="w"> </span><span class="nx">bucket</span><span class="p">.</span><span class="nx">sort</span><span class="p">((</span><span class="nx">a</span><span class="p">,</span><span class="w"> </span><span class="nx">b</span><span class="p">)</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="nx">a</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="nx">b</span><span class="p">);</span>
<a id="__codelineno-4-20" name="__codelineno-4-20" href="#__codelineno-4-20"></a><span class="w"> </span><span class="p">}</span>
<a id="__codelineno-4-21" name="__codelineno-4-21" href="#__codelineno-4-21"></a><span class="w"> </span><span class="c1">// 3. 遍历桶合并结果</span>
@ -1932,7 +1908,7 @@
<a id="__codelineno-5-15" name="__codelineno-5-15" href="#__codelineno-5-15"></a><span class="w"> </span><span class="p">}</span>
<a id="__codelineno-5-16" name="__codelineno-5-16" href="#__codelineno-5-16"></a><span class="w"> </span><span class="c1">// 2. 对各个桶执行排序</span>
<a id="__codelineno-5-17" name="__codelineno-5-17" href="#__codelineno-5-17"></a><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="kd">const</span><span class="w"> </span><span class="nx">bucket</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="nx">buckets</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<a id="__codelineno-5-18" name="__codelineno-5-18" href="#__codelineno-5-18"></a><span class="w"> </span><span class="c1">// 使用内置排序函数,也可以替换成其排序算法</span>
<a id="__codelineno-5-18" name="__codelineno-5-18" href="#__codelineno-5-18"></a><span class="w"> </span><span class="c1">// 使用内置排序函数,也可以替换成其排序算法</span>
<a id="__codelineno-5-19" name="__codelineno-5-19" href="#__codelineno-5-19"></a><span class="w"> </span><span class="nx">bucket</span><span class="p">.</span><span class="nx">sort</span><span class="p">((</span><span class="nx">a</span><span class="p">,</span><span class="w"> </span><span class="nx">b</span><span class="p">)</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="nx">a</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="nx">b</span><span class="p">);</span>
<a id="__codelineno-5-20" name="__codelineno-5-20" href="#__codelineno-5-20"></a><span class="w"> </span><span class="p">}</span>
<a id="__codelineno-5-21" name="__codelineno-5-21" href="#__codelineno-5-21"></a><span class="w"> </span><span class="c1">// 3. 遍历桶合并结果</span>
@ -1968,7 +1944,7 @@
<a id="__codelineno-8-12" name="__codelineno-8-12" href="#__codelineno-8-12"></a> <span class="p">}</span>
<a id="__codelineno-8-13" name="__codelineno-8-13" href="#__codelineno-8-13"></a> <span class="c1">// 2. 对各个桶执行排序</span>
<a id="__codelineno-8-14" name="__codelineno-8-14" href="#__codelineno-8-14"></a> <span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="n">buckets</span><span class="p">.</span><span class="bp">indices</span> <span class="p">{</span>
<a id="__codelineno-8-15" name="__codelineno-8-15" href="#__codelineno-8-15"></a> <span class="c1">// 使用内置排序函数,也可以替换成其排序算法</span>
<a id="__codelineno-8-15" name="__codelineno-8-15" href="#__codelineno-8-15"></a> <span class="c1">// 使用内置排序函数,也可以替换成其排序算法</span>
<a id="__codelineno-8-16" name="__codelineno-8-16" href="#__codelineno-8-16"></a> <span class="n">buckets</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="bp">sort</span><span class="p">()</span>
<a id="__codelineno-8-17" name="__codelineno-8-17" href="#__codelineno-8-17"></a> <span class="p">}</span>
<a id="__codelineno-8-18" name="__codelineno-8-18" href="#__codelineno-8-18"></a> <span class="c1">// 3. 遍历桶合并结果</span>
@ -1989,21 +1965,21 @@
</div>
</div>
<div class="admonition question">
<p class="admonition-title">桶排序的用场景是什么?</p>
<p>桶排序一般用于排序超大体量的数据。例如输入数据包含 100 万个元素,由于空间限,系统无法一次性所有数据加载进内存,那么可以将数据划分到 1000 个桶里,再依次排序每个桶,最终合并结果即可</p>
<p class="admonition-title">桶排序的用场景是什么?</p>
<p>桶排序适用于处理体量很大的数据。例如输入数据包含 100 万个元素,由于空间限,系统内存无法一次性加载所有数据。此时,可以将数据分成 1000 个桶,然后分别对每个桶进行排序,最后将结果合并</p>
</div>
<h2 id="1162">11.6.2. &nbsp; 算法特性<a class="headerlink" href="#1162" title="Permanent link">&para;</a></h2>
<p><strong>时间复杂度 <span class="arithmatex">\(O(n + k)\)</span></strong> :假设元素平均分布在各个桶内,则每个桶内元素数量为 <span class="arithmatex">\(\frac{n}{k}\)</span> 。假设排序单个桶使用 <span class="arithmatex">\(O(\frac{n}{k} \log\frac{n}{k})\)</span> 时间,则排序所有桶使用 <span class="arithmatex">\(O(n \log\frac{n}{k})\)</span> 时间<strong>当桶数量 <span class="arithmatex">\(k\)</span> 比较大时,时间复杂度则趋向于 <span class="arithmatex">\(O(n)\)</span></strong>最后合并结果需要遍历 <span class="arithmatex">\(n\)</span> 个桶,使用 <span class="arithmatex">\(O(k)\)</span> 时间。</p>
<p>最差情况下,所有数据被分配到一个桶中,且排序算法退化至 <span class="arithmatex">\(O(n^2)\)</span> ,此时使用 <span class="arithmatex">\(O(n^2)\)</span> 时间,因此是“自适应排序”。</p>
<p><strong>空间复杂度 <span class="arithmatex">\(O(n + k)\)</span></strong> :需要借助 <span class="arithmatex">\(k\)</span> 个桶和共 <span class="arithmatex">\(n\)</span> 个元素的额外空间,“非原地排序”。</p>
<p><strong>时间复杂度 <span class="arithmatex">\(O(n + k)\)</span></strong> :假设元素在各个桶内平均分布,那么每个桶内元素数量为 <span class="arithmatex">\(\frac{n}{k}\)</span> 。假设排序单个桶使用 <span class="arithmatex">\(O(\frac{n}{k} \log\frac{n}{k})\)</span> 时间,则排序所有桶使用 <span class="arithmatex">\(O(n \log\frac{n}{k})\)</span> 时间<strong>当桶数量 <span class="arithmatex">\(k\)</span> 比较大时,时间复杂度则趋向于 <span class="arithmatex">\(O(n)\)</span></strong> 。合并结果需要遍历 <span class="arithmatex">\(n\)</span> 个桶,花费 <span class="arithmatex">\(O(k)\)</span> 时间。</p>
<p>在最坏情况下,所有数据被分配到一个桶中,且排序该桶使用 <span class="arithmatex">\(O(n^2)\)</span> 时间,因此是“自适应排序”。</p>
<p><strong>空间复杂度 <span class="arithmatex">\(O(n + k)\)</span></strong> :需要借助 <span class="arithmatex">\(k\)</span> 个桶和<span class="arithmatex">\(n\)</span> 个元素的额外空间,属于“非原地排序”。</p>
<p>桶排序是否稳定取决于排序桶内元素的算法是否稳定。</p>
<h2 id="1163">11.6.3. &nbsp; 如何实现平均分配<a class="headerlink" href="#1163" title="Permanent link">&para;</a></h2>
<p>桶排序的时间复杂度理论上可以达到 <span class="arithmatex">\(O(n)\)</span> <strong>难点是需要将元素均匀分配到各个桶中</strong>,因为现实中的数据往往不是均匀分布的。举个例子,假设我们想要淘宝的所有商品根据价格范围平均分配到 10 个桶中,然而商品价格不是均匀分布的,100 元以下非常多1000 元以上非常少;如果我们将价格区间平均划为 10 份,那么各个桶的商品数量差距会非常大。</p>
<p>实现平均分配,我们可以先大致设置一个分界线,将数据粗略分到 3 个桶,分配完后,<strong>再把商品较多的桶继续划分为 3 个桶,直至所有桶元素数量大致平均为止</strong>方法本质上是生成一个递归树,叶节点的值尽平均。当然,不一定要划分为 3 个桶,可根据数据特点灵活选</p>
<p>桶排序的时间复杂度理论上可以达到 <span class="arithmatex">\(O(n)\)</span> <strong>关键在于将元素均匀分配到各个桶中</strong>,因为实际数据往往不是均匀分布的。例如,我们想要淘宝的所有商品价格范围平均分配到 10 个桶中,商品价格分布不均,低于 100 元非常多,高于 1000 元非常少。若将价格区间平均划为 10 份,各个桶的商品数量差距会非常大。</p>
<p>为实现平均分配,我们可以先设定一个大致的分界线,将数据粗略分到 3 个桶中。<strong>分配完毕后,再将商品较多的桶继续划分为 3 个桶,直至所有桶中的元素数量大致相等</strong>这种方法本质上是创建一个递归树,使叶节点的值尽可能平均。当然,不一定要每轮将数据划分为 3 个桶,具体划分方式可根据数据特点灵活选</p>
<p><img alt="递归划分桶" src="../bucket_sort.assets/scatter_in_buckets_recursively.png" /></p>
<p align="center"> Fig. 递归划分桶 </p>
<p>如果我们提前知道商品价格的概率分布,<strong>那么也可以根据数据概率分布设置每个桶的价格分界线</strong>注意,数据分布不一定需要特意统计,也可以根据数据特点采用某种概率模型近似。如下图所示,我们假设商品价格服从正态分布,就可以合理设置价格区间,将商品平均分配到各个桶中。</p>
<p>如果我们提前知道商品价格的概率分布,<strong>可以根据数据概率分布设置每个桶的价格分界线</strong>值得注意的是,数据分布不一定需要特意统计,也可以根据数据特点采用某种概率模型进行近似。如下图所示,我们假设商品价格服从正态分布,这样就可以合理地设定价格区间,从而将商品平均分配到各个桶中。</p>
<p><img alt="根据概率分布划分桶" src="../bucket_sort.assets/scatter_in_buckets_distribution.png" /></p>
<p align="center"> Fig. 根据概率分布划分桶 </p>

View File

@ -1768,13 +1768,13 @@
<h1 id="117">11.7. &nbsp; 计数排序<a class="headerlink" href="#117" title="Permanent link">&para;</a></h1>
<p>顾名思义,「计数排序 Counting Sort」通过统计元素数量来实现排序一般应用于整数数组。</p>
<p>「计数排序 Counting Sort」通过统计元素数量来实现排序通常应用于整数数组。</p>
<h2 id="1171">11.7.1. &nbsp; 简单实现<a class="headerlink" href="#1171" title="Permanent link">&para;</a></h2>
<p>先看一个简单例子。给定一个长度为 <span class="arithmatex">\(n\)</span> 的数组 <code>nums</code> 元素皆为 <strong>非负整数</strong>。计数排序的整体流程</p>
<p>看一个简单例子。给定一个长度为 <span class="arithmatex">\(n\)</span> 的数组 <code>nums</code> 其中的元素都是“非负整数”。计数排序的整体流程如下</p>
<ol>
<li>遍历记录数组中的最大数字,记为 <span class="arithmatex">\(m\)</span> 并建立一个长度为 <span class="arithmatex">\(m + 1\)</span> 的辅助数组 <code>counter</code> </li>
<li><strong>借助 <code>counter</code> 统计 <code>nums</code> 中各数字的出现次数</strong>,其中 <code>counter[num]</code> 对应数字 <code>num</code> 的出现次数。统计方法很简单,只需遍历 <code>nums</code> (设当前数字为 <code>num</code>),每轮将 <code>counter[num]</code> <span class="arithmatex">\(1\)</span> 即可。</li>
<li><strong>由于 <code>counter</code> 的各个索引天然有序,因此相当于所有数字已经被排序好了</strong>。接下来,我们遍历 <code>counter</code> ,根据各数字的出现次数,将各数字按从小到大的顺序填入 <code>nums</code> 即可。</li>
<li>遍历数组,找出数组中的最大数字,记为 <span class="arithmatex">\(m\)</span> 然后创建一个长度为 <span class="arithmatex">\(m + 1\)</span> 的辅助数组 <code>counter</code> </li>
<li><strong>借助 <code>counter</code> 统计 <code>nums</code> 中各数字的出现次数</strong>,其中 <code>counter[num]</code> 对应数字 <code>num</code> 的出现次数。统计方法很简单,只需遍历 <code>nums</code>(设当前数字为 <code>num</code>),每轮将 <code>counter[num]</code> <span class="arithmatex">\(1\)</span> 即可。</li>
<li><strong>由于 <code>counter</code> 的各个索引天然有序,因此相当于所有数字已经被排序好了</strong>。接下来,我们遍历 <code>counter</code> ,根据各数字的出现次数,将它们按从小到大的顺序填入 <code>nums</code> 即可。</li>
</ol>
<p><img alt="计数排序流程" src="../counting_sort.assets/counting_sort_overview.png" /></p>
<p align="center"> Fig. 计数排序流程 </p>
@ -1968,20 +1968,20 @@
</div>
<div class="admonition note">
<p class="admonition-title">计数排序与桶排序的联系</p>
<p>从桶排序的角度看,我们可以计数排序中计数数组 <code>counter</code> 的每个索引想象成一个桶,将统计数量的过程想象成把各个元素分配到对应的桶中。本质上,计数排序是桶排序在整型数据下的一个特例。</p>
<p>从桶排序的角度看,我们可以计数排序中计数数组 <code>counter</code> 的每个索引视为一个桶,将统计数量的过程看作是将各个元素分配到对应的桶中。本质上,计数排序是桶排序在整型数据下的一个特例。</p>
</div>
<h2 id="1172">11.7.2. &nbsp; 完整实现<a class="headerlink" href="#1172" title="Permanent link">&para;</a></h2>
<p>细心的同学可能发现,<strong>如果输入数据是对象,上述步骤 <code>3.</code> 就失效了</strong>。例如输入数据是商品对象,我们想要按照商品价格(类的成员变量)对商品进行排序,而上述算法只能给出价格的排序结果。</p>
<p>那么如何才能得到原数据的排序结果呢?我们首先计算 <code>counter</code> 的「前缀和」顾名思义,索引 <code>i</code> 处的前缀和 <code>prefix[i]</code> 等于数组前 <code>i</code> 个元素之和,即</p>
<p>细心的同学可能发现,<strong>如果输入数据是对象,上述步骤 <code>3.</code> 就失效了</strong>。例如输入数据是商品对象,我们想要按照商品价格(类的成员变量)对商品进行排序,而上述算法只能给出价格的排序结果。</p>
<p>那么如何才能得到原数据的排序结果呢?我们首先计算 <code>counter</code> 的「前缀和」顾名思义,索引 <code>i</code> 处的前缀和 <code>prefix[i]</code> 等于数组前 <code>i</code> 个元素之和,即</p>
<div class="arithmatex">\[
\text{prefix}[i] = \sum_{j=0}^i \text{counter[j]}
\]</div>
<p><strong>前缀和具有明确意义,<code>prefix[num] - 1</code> 代表元素 <code>num</code> 在结果数组 <code>res</code> 中最后一次出现的索引</strong>。这个信息关键,因为其给出了各个元素应该出现在结果数组的哪个位置。接下来,我们倒序遍历原数组 <code>nums</code> 的每个元素 <code>num</code> ,在每轮迭代中执行:</p>
<p><strong>前缀和具有明确意义,<code>prefix[num] - 1</code> 代表元素 <code>num</code> 在结果数组 <code>res</code> 中最后一次出现的索引</strong>。这个信息非常关键,因为它告诉我们各个元素应该出现在结果数组的哪个位置。接下来,我们倒序遍历原数组 <code>nums</code> 的每个元素 <code>num</code> ,在每轮迭代中执行:</p>
<ol>
<li><code>num</code> 填入数组 <code>res</code> 的索引 <code>prefix[num] - 1</code> 处;</li>
<li>令前缀和 <code>prefix[num]</code> <span class="arithmatex">\(1\)</span> ,从而得到下次放置 <code>num</code> 的索引;</li>
<li>令前缀和 <code>prefix[num]</code> <span class="arithmatex">\(1\)</span> ,从而得到下次放置 <code>num</code> 的索引;</li>
</ol>
<p>完成遍历后,数组 <code>res</code> 中就是排序好的结果,最后使用 <code>res</code> 覆盖原数组 <code>nums</code> 即可</p>
<p>遍历完成后,数组 <code>res</code> 中就是排序好的结果,最后使用 <code>res</code> 覆盖原数组 <code>nums</code> 即可</p>
<div class="tabbed-set tabbed-alternate" data-tabs="2:8"><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" /><div class="tabbed-labels"><label for="__tabbed_2_1">&lt;1&gt;</label><label for="__tabbed_2_2">&lt;2&gt;</label><label for="__tabbed_2_3">&lt;3&gt;</label><label for="__tabbed_2_4">&lt;4&gt;</label><label for="__tabbed_2_5">&lt;5&gt;</label><label for="__tabbed_2_6">&lt;6&gt;</label><label for="__tabbed_2_7">&lt;7&gt;</label><label for="__tabbed_2_8">&lt;8&gt;</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
@ -2268,13 +2268,13 @@
</div>
</div>
<h2 id="1173">11.7.3. &nbsp; 算法特性<a class="headerlink" href="#1173" title="Permanent link">&para;</a></h2>
<p><strong>时间复杂度 <span class="arithmatex">\(O(n + m)\)</span></strong> :涉及遍历 <code>nums</code> 和遍历 <code>counter</code> ,都使用线性时间。一般情况下 <span class="arithmatex">\(n \gg m\)</span> 此时使用线性 <span class="arithmatex">\(O(n)\)</span> 时间</p>
<p><strong>空间复杂度 <span class="arithmatex">\(O(n + m)\)</span></strong> :借助了长度分别为 <span class="arithmatex">\(n\)</span> , <span class="arithmatex">\(m\)</span> 的数组 <code>res</code><code>counter</code> ,是“非原地排序”。</p>
<p><strong>稳定排序</strong>:由于向 <code>res</code> 中填充元素的顺序是“从右向左”的,因此倒序遍历 <code>nums</code> 可以避免改变相等元素之间的相对位置,从而实现“稳定排序”;其实正序遍历 <code>nums</code> 也可以得到正确的排序结果,但结果“非稳定”。</p>
<p><strong>时间复杂度 <span class="arithmatex">\(O(n + m)\)</span></strong> :涉及遍历 <code>nums</code> 和遍历 <code>counter</code> ,都使用线性时间。一般情况下 <span class="arithmatex">\(n \gg m\)</span> 时间复杂度趋于 <span class="arithmatex">\(O(n)\)</span></p>
<p><strong>空间复杂度 <span class="arithmatex">\(O(n + m)\)</span></strong> :借助了长度分别为 <span class="arithmatex">\(n\)</span> <span class="arithmatex">\(m\)</span> 的数组 <code>res</code><code>counter</code> 因此是“非原地排序”。</p>
<p><strong>稳定排序</strong>:由于向 <code>res</code> 中填充元素的顺序是“从右向左”的,因此倒序遍历 <code>nums</code> 可以避免改变相等元素之间的相对位置,从而实现“稳定排序”。实际上,正序遍历 <code>nums</code> 也可以得到正确的排序结果,但结果“非稳定”</p>
<h2 id="1174">11.7.4. &nbsp; 局限性<a class="headerlink" href="#1174" title="Permanent link">&para;</a></h2>
<p>看到这里,你也许会觉得计数排序太妙了,咔咔一通操作,时间复杂度就下来了。然而,使用计数排序的前置条件比较苛刻</p>
<p><strong>计数排序只适用于非负整数</strong>。若想要用在其他类型数据上,则要求该数据必须可以被转为非负整数,并且不能改变各个元素之间的相对大小关系。例如,对于包含负数的整数数组,可以先给所有数字加上一个常数,将全部数字转化为正数,排序完成后再转换回去即可。</p>
<p><strong>计数排序适用于数据量大但数据范围不大的情况</strong>。比如,上述示例中 <span class="arithmatex">\(m\)</span> 不能太大,否则占用空间太多;而当 <span class="arithmatex">\(n \ll m\)</span> 时,计数排序使用 <span class="arithmatex">\(O(m)\)</span> 时间,可能比 <span class="arithmatex">\(O(n \log n)\)</span> 的排序算法还要慢。</p>
<p>看到这里,你也许会觉得计数排序非常巧妙,仅通过统计数量就可以实现高效的排序工作。然而,使用计数排序的前置条件相对较为严格</p>
<p><strong>计数排序只适用于非负整数</strong>。若想要将其用于其他类型数据,需要确保这些数据可以被转为非负整数,并且在转换过程中不能改变各个元素之间的相对大小关系。例如,对于包含负数的整数数组,可以先给所有数字加上一个常数,将全部数字转化为正数,排序完成后再转换回去即可。</p>
<p><strong>计数排序适用于数据量大但数据范围较小的情况</strong>。比如,上述示例中 <span class="arithmatex">\(m\)</span> 不能太大,否则占用过多空间。而当 <span class="arithmatex">\(n \ll m\)</span> 时,计数排序使用 <span class="arithmatex">\(O(m)\)</span> 时间,可能比 <span class="arithmatex">\(O(n \log n)\)</span> 的排序算法还要慢。</p>

View File

@ -1754,18 +1754,17 @@
<h1 id="113">11.3. &nbsp; 插入排序<a class="headerlink" href="#113" title="Permanent link">&para;</a></h1>
<p>「插入排序 Insertion Sort」是一种基于 <strong>数组插入操作</strong> 的排序算法</p>
<p>「插入操作」原理:选定某个待排序元素为基准数 <code>base</code><code>base</code> 与其左侧已排序区间元素依次对比大小,并插入到正确位置</p>
<p>回忆数组插入操作,我们需要将从目标索引到 <code>base</code> 之间的所有元素向右移动一位,然后再将 <code>base</code> 赋值给目标索引。</p>
<p>「插入排序 Insertion Sort」是一种基于数组插入操作的排序算法。具体来说,选择一个待排序的元素作为基准值 <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 align="center"> Fig. 单次插入操作 </p>
<h2 id="1131">11.3.1. &nbsp; 算法流程<a class="headerlink" href="#1131" title="Permanent link">&para;</a></h2>
<p>循环执行插入操作</p>
<p>插入排序的整体流程如下</p>
<ol>
<li>选取数组的 <strong>第 2 个元素</strong> <code>base</code> ,执行插入操作后,<strong>数组前 2 个元素已完成排序</strong></li>
<li>选取 <strong>第 3 个元素</strong> <code>base</code> ,执行插入操作后,<strong>数组前 3 个元素已完成排序</strong></li>
<li>以此类推……最后一轮选取 <strong>数组尾元素</strong> <code>base</code> ,执行插入操作后,<strong>所有元素已完成排序</strong></li>
<li>首先,选取数组的第 2 个元素作<code>base</code> ,执行插入操作后,<strong>数组前 2 个元素已排序</strong></li>
<li>接着,选取第 3 个元素作<code>base</code> ,执行插入操作后,<strong>数组前 3 个元素已排序</strong></li>
<li>以此类推,在最后一轮中,选取数组尾元素作<code>base</code> ,执行插入操作后,<strong>所有元素已排序</strong></li>
</ol>
<p><img alt="插入排序流程" src="../insertion_sort.assets/insertion_sort_overview.png" /></p>
<p align="center"> Fig. 插入排序流程 </p>
@ -1928,21 +1927,21 @@
</div>
</div>
<h2 id="1132">11.3.2. &nbsp; 算法特性<a class="headerlink" href="#1132" title="Permanent link">&para;</a></h2>
<p><strong>时间复杂度 <span class="arithmatex">\(O(n^2)\)</span></strong> :最差情况下,各轮插入操作循环 <span class="arithmatex">\(n - 1\)</span> , <span class="arithmatex">\(n-2\)</span> , <span class="arithmatex">\(\cdots\)</span> , <span class="arithmatex">\(2\)</span> , <span class="arithmatex">\(1\)</span> 次,求和 <span class="arithmatex">\(\frac{(n - 1) n}{2}\)</span> 使用 <span class="arithmatex">\(O(n^2)\)</span> 时间。输入数组完全有序下,达到最佳时间复杂度 <span class="arithmatex">\(O(n)\)</span> ,因此是“自适应排序”。</p>
<p><strong>空间复杂度 <span class="arithmatex">\(O(1)\)</span></strong> :指针 <span class="arithmatex">\(i\)</span> , <span class="arithmatex">\(j\)</span> 使用常数大小的额外空间,因此是“原地排序”。</p>
<p>在插入操作中,我们会将元素插入到相等元素的右,不会改变它们的序,因此是“稳定排序”。</p>
<p><strong>时间复杂度 <span class="arithmatex">\(O(n^2)\)</span></strong> :最差情况下,每次插入操作分别需要循环 <span class="arithmatex">\(n - 1\)</span> , <span class="arithmatex">\(n-2\)</span> , <span class="arithmatex">\(\cdots\)</span> , <span class="arithmatex">\(2\)</span> , <span class="arithmatex">\(1\)</span> 次,求和得到 <span class="arithmatex">\(\frac{(n - 1) n}{2}\)</span> 因此时间复杂度为 <span class="arithmatex">\(O(n^2)\)</span>输入数组完全有序时,插入排序达到最佳时间复杂度 <span class="arithmatex">\(O(n)\)</span> ,因此是“自适应排序”。</p>
<p><strong>空间复杂度 <span class="arithmatex">\(O(1)\)</span></strong> :指针 <span class="arithmatex">\(i\)</span> , <span class="arithmatex">\(j\)</span> 使用常数大小的额外空间,所以插入排序是“原地排序”。</p>
<p>在插入操作过程中,我们会将元素插入到相等元素的右,不会改变它们的序,因此是“稳定排序”。</p>
<h2 id="1133">11.3.3. &nbsp; 插入排序优势<a class="headerlink" href="#1133" title="Permanent link">&para;</a></h2>
<p>回顾冒泡排序」和「插入排序的复杂度分析,两者的循环轮数都是 <span class="arithmatex">\(\frac{(n - 1) n}{2}\)</span>但不同的是</p>
<p>回顾冒泡排序插入排序的复杂度分析,两者的循环轮数都是 <span class="arithmatex">\(\frac{(n - 1) n}{2}\)</span>然而,它们之间存在以下差异</p>
<ul>
<li>冒泡操作基于 <strong>元素交换</strong> 实现,需要借助一个临时变量实现,共 3 个单元操作;</li>
<li>插入操作基于 <strong>元素赋值</strong> 实现,需 1 个单元操作;</li>
<li>冒泡操作基于元素交换实现,需要借助一个临时变量,共涉及 3 个单元操作;</li>
<li>插入操作基于元素赋值实现,需 1 个单元操作;</li>
</ul>
<p>粗略估计,冒泡排序的计算开销约为插入排序的 3 倍,因此插入排序更受欢迎,许多编程语言(如 Java的内置排序函数都使用到了插入排序,大致思路为:</p>
<p>粗略估计下来,冒泡排序的计算开销约为插入排序的 3 倍,因此插入排序更受欢迎。实际上,许多编程语言(如 Java的内置排序函数都采用了插入排序,大致思路为:</p>
<ul>
<li>对于 <strong>长数组</strong>,采用基于分治的排序算法,例如「快速排序」,时间复杂度为 <span class="arithmatex">\(O(n \log n)\)</span> </li>
<li>对于 <strong>短数组</strong>,直接使用「插入排序」,时间复杂度为 <span class="arithmatex">\(O(n^2)\)</span> </li>
<li>对于长数组,采用基于分治的排序算法,例如「快速排序」,时间复杂度为 <span class="arithmatex">\(O(n \log n)\)</span> </li>
<li>对于短数组,直接使用「插入排序」,时间复杂度为 <span class="arithmatex">\(O(n^2)\)</span> </li>
</ul>
<p>虽然插入排序比快速排序的时间复杂度高,<strong>实际上在数据量较小插入排序更快</strong>这是因为复杂度中的常数项(即每轮中的单元操作数量)主导作用。这个现象与「线性查找」和「二分查找」的情况似。</p>
<p>尽管插入排序的时间复杂度高于快速排序<strong>但在数据量较小的情况下,插入排序实际上更快</strong>这是因为在数据量较小时,复杂度中的常数项(即每轮中的单元操作数量)主导作用。这个现象与「线性查找」和「二分查找」的情况似。</p>

View File

@ -1740,18 +1740,19 @@
<h1 id="111">11.1. &nbsp; 排序简介<a class="headerlink" href="#111" title="Permanent link">&para;</a></h1>
<p>「排序算法 Sorting Algorithm」使列表中的所有元素按照从小到大的顺序排列。</p>
<p>「排序算法 Sorting Algorithm」使列表中的所有元素按照序排列。</p>
<ul>
<li>待排序列表的 <strong>元素类型</strong> 可以是整数、浮点数、字符或字符串;</li>
<li>排序算法可根据需设定 <strong>判断规则</strong>,例如数字大小、字符 ASCII 码顺序自定义规则;</li>
<li>待排序列表的元素类型可以是整数、浮点数、字符或字符串</li>
<li>排序算法可根据需设定判断规则,如数字大小、字符 ASCII 码顺序自定义规则;</li>
</ul>
<p><img alt="排序中不同的元素类型和判断规则" src="../intro_to_sort.assets/sorting_examples.png" /></p>
<p align="center"> Fig. 排序中不同的元素类型和判断规则 </p>
<h2 id="1111">11.1.1. &nbsp; 评价维度<a class="headerlink" href="#1111" title="Permanent link">&para;</a></h2>
<p><strong>运行效率</strong>:我们望排序算法的时间复杂度尽可能低,且总体操作数量少(即时间复杂度中的常数项低)。大数据量,运行效率尤为重要。</p>
<p><strong>就地性</strong>:顾名思义,「原地排序」直接在原数组上操作实现排序,而不用借助额外辅助数组,节约内存;并且一般情况下,原地排序的数据搬运操作较少,运行速度也更快。</p>
<p><strong>稳定性</strong>:「稳定排序」在完成排序后,相等元素在数组中的相对顺序 <strong>不会发生改变</strong>。假设我们有一个存储学生信息的表格,第 1, 2 列分别是姓名和年龄。那么在以下示例中,「非稳定排序」会导致输入数据的有序性丢失。稳定性是排序算法很好的特性,<strong>在多级排序中是必须的</strong></p>
<p><strong>运行效率</strong>:我们望排序算法的时间复杂度尽低,且总体操作数量少(即时间复杂度中的常数项低)。对于大数据量情况,运行效率显得尤为重要。</p>
<p><strong>就地性</strong>:顾名思义,「原地排序」通过在原数组上直接操作实现排序,无需借助额外辅助数组,从而节省内存。通常情况下,原地排序的数据搬运操作较少,运行速度也更快。</p>
<p><strong>稳定性</strong>:「稳定排序」在完成排序后,相等元素在数组中的相对顺序不发生改变。稳定排序是优良特性,也是多级排序场景的必要条件</p>
<p>假设我们有一个存储学生信息的表格,第 1, 2 列分别是姓名和年龄。在这种情况下,「非稳定排序」可能导致输入数据的有序性丧失。</p>
<div class="highlight"><pre><span></span><code><a id="__codelineno-0-1" name="__codelineno-0-1" href="#__codelineno-0-1"></a><span class="c1"># 输入数据是按照姓名排序好的</span>
<a id="__codelineno-0-2" name="__codelineno-0-2" href="#__codelineno-0-2"></a><span class="c1"># (name, age)</span>
<a id="__codelineno-0-3" name="__codelineno-0-3" href="#__codelineno-0-3"></a><span class="w"> </span><span class="o">(</span><span class="s1">&#39;A&#39;</span>,<span class="w"> </span><span class="m">19</span><span class="o">)</span>
@ -1769,11 +1770,12 @@
<a id="__codelineno-0-15" name="__codelineno-0-15" href="#__codelineno-0-15"></a><span class="w"> </span><span class="o">(</span><span class="s1">&#39;C&#39;</span>,<span class="w"> </span><span class="m">21</span><span class="o">)</span>
<a id="__codelineno-0-16" name="__codelineno-0-16" href="#__codelineno-0-16"></a><span class="w"> </span><span class="o">(</span><span class="s1">&#39;E&#39;</span>,<span class="w"> </span><span class="m">23</span><span class="o">)</span>
</code></pre></div>
<p><strong>自适应性</strong>:「自适应排序」的时间复杂度受输入数据影响,即最佳、最差、平均时间复杂度不全部相等。自适应性也要分情况对待,若最差时间复杂度差于平均时间复杂度,代表排序算法会在某些数据下发生劣化,因此是负面性质;而若最佳时间复杂度优于平均时间复杂度,则是正面性质</p>
<p><strong>是否基于比较</strong>:「比较排序」是根据比较算子(<span class="arithmatex">\(&lt;\)</span> , <span class="arithmatex">\(=\)</span> , <span class="arithmatex">\(&gt;\)</span>)来判断元素的相对顺序,进而排序整个数组,理论最优时间复杂度为 <span class="arithmatex">\(O(n \log n)\)</span> 。「非比较排序」不采用,时间复杂度可以达到 <span class="arithmatex">\(O(n)\)</span> ,但通用性相对较差</p>
<p><strong>自适应性</strong>:「自适应排序」的时间复杂度受输入数据影响,即最佳、最差、平均时间复杂度并不完全相等</p>
<p>自适应性需要根据具体情况来评估。如果最差时间复杂度差于平均时间复杂度,说明排序算法在某些数据下性能可能劣化,因此被视为负面属性;而如果最佳时间复杂度优于平均时间复杂度,则被视为正面属性</p>
<p><strong>是否基于比较</strong>:「基于比较的排序」依赖于比较运算符(<span class="arithmatex">\(&lt;\)</span> , <span class="arithmatex">\(=\)</span> , <span class="arithmatex">\(&gt;\)</span>)来判断元素的相对顺序,从而排序整个数组,理论最优时间复杂度为 <span class="arithmatex">\(O(n \log n)\)</span> 。而「非比较排序」不使用比较运算符,时间复杂度可达 <span class="arithmatex">\(O(n)\)</span> ,但其通用性相对较差。</p>
<h2 id="1112">11.1.2. &nbsp; 理想排序算法<a class="headerlink" href="#1112" title="Permanent link">&para;</a></h2>
<p><strong>运行快、原地、稳定、正向自适应、通用性好</strong>。显然,<strong>目前没有发现具备以上所有特性的排序算法</strong>,排序算法的选型使用取决于具体的数据特点问题特征</p>
<p>接下来,我们将一起学习各种排序算法,并基于上评价维度展开分析各个排序算法的优缺点。</p>
<p><strong>运行快、原地、稳定、正向自适应、通用性好</strong>。显然,迄今为止尚未发现兼具以上所有特性的排序算法。因此,在选择排序算法时,需要根据具体的数据特点问题需求来决定</p>
<p>接下来,我们将共同学习各种排序算法,并基于上评价维度各个排序算法的优缺点进行分析</p>

View File

@ -1754,22 +1754,21 @@
<h1 id="115">11.5. &nbsp; 归并排序<a class="headerlink" href="#115" title="Permanent link">&para;</a></h1>
<p>「归并排序 Merge Sort」是算法中“分治思想”的典型体现,其有「划分」和「合并两个阶段:</p>
<p>「归并排序 Merge Sort」基于分治思想实现排序,包含“划分”和“合并两个阶段:</p>
<ol>
<li><strong>划分阶段</strong>:通过递归不断 <strong>将数组从中点位置划分开</strong>,将长数组的排序问题转为短数组的排序问题;</li>
<li><strong>合并阶段</strong>划分到子数组长度为 1 时,开始向上合并,不断将 <strong>左、右两个短排序数组</strong> 合并为 <strong>一个长排序数组</strong>,直至合并至原数组时完成排序</li>
<li><strong>划分阶段</strong>:通过递归不断地将数组从中点处分开,将长数组的排序问题转为短数组的排序问题;</li>
<li><strong>合并阶段</strong>子数组长度为 1 时终止划分,开始合并,持续地将左右两个较短的有序数组合并为一个较长的有序数组,直至结束</li>
</ol>
<p><img alt="归并排序的划分与合并阶段" src="../merge_sort.assets/merge_sort_overview.png" /></p>
<p align="center"> Fig. 归并排序的划分与合并阶段 </p>
<h2 id="1151">11.5.1. &nbsp; 算法流程<a class="headerlink" href="#1151" title="Permanent link">&para;</a></h2>
<p><strong>「递归划分」</strong> 从顶至底递归地 <strong>将数组从中点切为两个子数组</strong>,直至长度为 1 </p>
<p>“划分阶段”从顶至底递归地将数组从中点切为两个子数组,直至长度为 1 </p>
<ol>
<li>计算数组中点 <code>mid</code> ,递归划分左子数组(区间 <code>[left, mid]</code> )和右子数组(区间 <code>[mid + 1, right]</code> </li>
<li>递归执行 <code>1.</code> 步骤,直至子数组区间长度为 1 时,终止递归划分;</li>
<li>递归执行步骤 <code>1.</code> ,直至子数组区间长度为 1 时,终止递归划分;</li>
</ol>
<p><strong>「回溯合并」</strong> 从底至顶地将左子数组和右子数组合并为一个 <strong>有序数组</strong> </p>
<p>需要注意,由于从长度为 1 的子数组开始合并,所以 <strong>每个子数组都是有序的</strong>。因此,合并任务本质是要 <strong>将两个有序子数组合并为一个有序数组</strong></p>
<p>“合并阶段”从底至顶地将左子数组和右子数组合并为一个有序数组。需要注意的是,从长度为 1 的子数组开始合并,合并阶段中的每个子数组都是有序的。</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">&lt;1&gt;</label><label for="__tabbed_1_2">&lt;2&gt;</label><label for="__tabbed_1_3">&lt;3&gt;</label><label for="__tabbed_1_4">&lt;4&gt;</label><label for="__tabbed_1_5">&lt;5&gt;</label><label for="__tabbed_1_6">&lt;6&gt;</label><label for="__tabbed_1_7">&lt;7&gt;</label><label for="__tabbed_1_8">&lt;8&gt;</label><label for="__tabbed_1_9">&lt;9&gt;</label><label for="__tabbed_1_10">&lt;10&gt;</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
@ -1804,10 +1803,10 @@
</div>
</div>
</div>
<p>观察发现,归并排序的递归顺序就是二叉树的后序遍历」。</p>
<p>观察发现,归并排序的递归顺序二叉树的后序遍历相同,具体来看:</p>
<ul>
<li><strong>后序遍历</strong>:先递归左子树再递归右子树最后处理根节点。</li>
<li><strong>归并排序</strong>:先递归左子树、再递归右子树、最后处理合并。</li>
<li><strong>后序遍历</strong>:先递归左子树再递归右子树最后处理根节点。</li>
<li><strong>归并排序</strong>:先递归左子数组,再递归右子数组,最后处理合并。</li>
</ul>
<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">
@ -2218,30 +2217,22 @@
</div>
</div>
</div>
<p>下面重点解释一下合并方法 <code>merge()</code> 的流程</p>
<ol>
<li>初始化一个辅助数组 <code>tmp</code> 暂存待合并区间 <code>[left, right]</code> 内的元素,后续通过覆盖原数组 <code>nums</code> 的元素来实现合并;</li>
<li>初始化指针 <code>i</code> , <code>j</code> , <code>k</code> 分别指向左子数组、右子数组、原数组的首元素;</li>
<li>循环判断 <code>tmp[i]</code><code>tmp[j]</code> 的大小,将较小的先覆盖至 <code>nums[k]</code> ,指针 <code>i</code> , <code>j</code> 根据判断结果交替前进(指针 <code>k</code> 也前进),直至两个子数组都遍历完,即可完成合并。</li>
</ol>
<p>合并方法 <code>merge()</code> 代码中的主要难点:</p>
<p>合并方法 <code>merge()</code> 代码中的难点包括</p>
<ul>
<li><code>nums</code> 的待合并区间为 <code>[left, right]</code> 而因为 <code>tmp</code> 复制了 <code>nums</code> 该区间元素,所以 <code>tmp</code> 对应区间为 <code>[0, right - left]</code> <strong>需要特别注意代码中各个变量的含义</strong></li>
<li>判断 <code>tmp[i]</code><code>tmp[j]</code> 的大小的操作中,还 <strong>需考虑子数组遍历完成后的索引越界问题</strong>,即 <code>i &gt; leftEnd</code><code>j &gt; rightEnd</code> 的情况索引越界的优先级是最高的,例如如果左子数组已经被合并完了,那么不用继续判断,直接合并右子数组元素即可。</li>
<li><strong>在阅读代码时,需要特别注意各个变量的含义</strong><code>nums</code> 的待合并区间为 <code>[left, right]</code> 但由于 <code>tmp</code> 复制了 <code>nums</code> 该区间元素,因此 <code>tmp</code> 对应区间为 <code>[0, right - left]</code></li>
<li>在比较 <code>tmp[i]</code><code>tmp[j]</code> 的大小时,<strong>需考虑子数组遍历完成后的索引越界问题</strong>,即 <code>i &gt; leftEnd</code><code>j &gt; rightEnd</code> 的情况索引越界的优先级是最高的,如果左子数组已经被合并完了,那么不需要继续比较,直接合并右子数组元素即可。</li>
</ul>
<h2 id="1152">11.5.2. &nbsp; 算法特性<a class="headerlink" href="#1152" title="Permanent link">&para;</a></h2>
<p><strong>时间复杂度 <span class="arithmatex">\(O(n \log n)\)</span></strong> :划分形成高度为 <span class="arithmatex">\(\log n\)</span> 的递归树,每层合并的总操作数量为 <span class="arithmatex">\(n\)</span> 总体使用 <span class="arithmatex">\(O(n \log n)\)</span> 时间</p>
<p><strong>空间复杂度 <span class="arithmatex">\(O(n)\)</span></strong> 需借助辅助数组实现合并,使用 <span class="arithmatex">\(O(n)\)</span> 大小的额外空间;递归深度为 <span class="arithmatex">\(\log n\)</span> ,使用 <span class="arithmatex">\(O(\log n)\)</span> 大小的栈帧空间因此是“非原地排序”。</p>
<p>在合并时,不改变相等元素的次序是“稳定排序”。</p>
<p><strong>时间复杂度 <span class="arithmatex">\(O(n \log n)\)</span></strong> :划分产生高度为 <span class="arithmatex">\(\log n\)</span> 的递归树,每层合并的总操作数量为 <span class="arithmatex">\(n\)</span> 因此总体时间复杂度为 <span class="arithmatex">\(O(n \log n)\)</span></p>
<p><strong>空间复杂度 <span class="arithmatex">\(O(n)\)</span></strong> :递归深度为 <span class="arithmatex">\(\log n\)</span> ,使用 <span class="arithmatex">\(O(\log n)\)</span> 大小的栈帧空间;合并操作需要借助辅助数组实现,使用 <span class="arithmatex">\(O(n)\)</span> 大小的额外空间;因此是“非原地排序”。</p>
<p>在合并过程中,相等元素的次序保持不变,因此归并排序是“稳定排序”。</p>
<h2 id="1153">11.5.3. &nbsp; 链表排序 *<a class="headerlink" href="#1153" title="Permanent link">&para;</a></h2>
<p>归并排序有一个很特别的优势,用于排序链表时有很好的性能表现,<strong>空间复杂度可优化至 <span class="arithmatex">\(O(1)\)</span></strong> ,这是因为</p>
<p>归并排序在排序链表时具有显著优势,空间复杂度可优化至 <span class="arithmatex">\(O(1)\)</span> ,原因如下</p>
<ul>
<li>由于链表可仅通过改变指针实现节点增删,因此将两个短有序链表合并为一个长有序链表无需使用额外空间,即回溯合并阶段不用像排序数组一样建立辅助数组 <code>tmp</code> </li>
<li>通过使用迭代」代替「递归划分,可省去递归使用的栈帧空间;</li>
<li>由于链表仅需改变指针就可实现节点增删操作,因此合并阶段(将两个短有序链表合并为一个长有序链表无需创建辅助链表。</li>
<li>通过使用迭代划分”替代“递归划分,可省去递归使用的栈帧空间;</li>
</ul>
<blockquote>
<p>详情参考:<a href="https://leetcode-cn.com/problems/sort-list/solution/sort-list-gui-bing-pai-xu-lian-biao-by-jyd/">148. 排序链表</a></p>
</blockquote>
<p>具体实现细节比较复杂,有兴趣的同学可以查阅相关资料进行学习。</p>

View File

@ -1782,14 +1782,14 @@
<h1 id="114">11.4. &nbsp; 快速排序<a class="headerlink" href="#114" title="Permanent link">&para;</a></h1>
<p>「快速排序 Quick Sort」是一种基于分治思想的排序算法,速度很快、应用很广</p>
<p>快速排序的核心操作「哨兵划分」,其目标:选数组某个元素<strong>基准数</strong>,将所有小于基准数的元素移动至其左边,大于基准数的元素移动至其右边。「哨兵划分」的实现流程为:</p>
<p>「快速排序 Quick Sort」是一种基于分治思想的排序算法运行高效,应用广泛</p>
<p>快速排序的核心操作「哨兵划分」,其目标:选数组中的某个元素作为“基准数”,将所有小于基准数的元素移到其左侧,而大于基准数的元素移到其右侧。具体来说,哨兵划分的流程为:</p>
<ol>
<li>数组最左端元素作为基准数,初始化两个指针 <code>i</code> , <code>j</code> 指向数组两端;</li>
<li>设置一个循环,每轮中使用 <code>i</code> / <code>j</code> 分别寻找个比基准数大 / 小的元素,并交换此两元素;</li>
<li>不断循环步骤 <code>2.</code> ,直 <code>i</code> , <code>j</code> 相遇时跳出,最终把基准数交换至两个子数组的分界线;</li>
<li>选取数组最左端元素作为基准数,初始化两个指针 <code>i</code> <code>j</code> 分别指向数组两端;</li>
<li>设置一个循环,每轮中使用 <code>i</code><code>j</code>分别寻找第一个比基准数大(小)的元素,然后交换这两个元素;</li>
<li>循环执行步骤 <code>2.</code> ,直 <code>i</code> <code>j</code> 相遇时停止,最后将基准数交换至两个子数组的分界线;</li>
</ol>
<p>哨兵划分」执行完毕后,原数组被划分成两个部分,即 <strong>左子数组</strong> <strong>右子数组</strong>,且满足 <strong>左子数组任意元素 &lt; 基准数 &lt; 右子数组任意元素</strong>。因此,接下来我们只需要排序两个子数组即可</p>
<p>哨兵划分完成后,原数组被划分成三部分:左子数组、基准数、右子数组,且满足“左子数组任意元素 <span class="arithmatex">\(\leq\)</span> 基准数 <span class="arithmatex">\(\leq\)</span> 右子数组任意元素”。因此,我们接下来只需对这两个子数组进行排序</p>
<div class="tabbed-set tabbed-alternate" data-tabs="1:9"><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" /><div class="tabbed-labels"><label for="__tabbed_1_1">&lt;1&gt;</label><label for="__tabbed_1_2">&lt;2&gt;</label><label for="__tabbed_1_3">&lt;3&gt;</label><label for="__tabbed_1_4">&lt;4&gt;</label><label for="__tabbed_1_5">&lt;5&gt;</label><label for="__tabbed_1_6">&lt;6&gt;</label><label for="__tabbed_1_7">&lt;7&gt;</label><label for="__tabbed_1_8">&lt;8&gt;</label><label for="__tabbed_1_9">&lt;9&gt;</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
@ -1823,7 +1823,7 @@
</div>
<div class="admonition note">
<p class="admonition-title">快速排序的分治思想</p>
<p>哨兵划分的实质是将 <strong>一个长数组的排序问题</strong> 简化为 <strong>两个短数组的排序问题</strong></p>
<p>哨兵划分的实质是将一个长数组的排序问题简化为两个短数组的排序问题。</p>
</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">
@ -2052,11 +2052,10 @@
</div>
<h2 id="1141">11.4.1. &nbsp; 算法流程<a class="headerlink" href="#1141" title="Permanent link">&para;</a></h2>
<ol>
<li>首先,对数组执行一次「哨兵划分」,得到待排序的 <strong>左子数组</strong><strong>右子数组</strong></li>
<li>接下来,对 <strong>左子数组</strong><strong>右子数组</strong> 分别 <strong>递归执行</strong>「哨兵划分」……</li>
<li>直至子数组长度为 1 时 <strong>终止递归</strong>,即可完成整个数组的排序;</li>
<li>首先,对数组执行一次「哨兵划分」,得到待排序的左子数组和右子数组</li>
<li>然后,对左子数组和右子数组分别递归执行「哨兵划分」</li>
<li>持续递归,直至子数组长度为 1 时终止,从而完成整个数组的排序;</li>
</ol>
<p>观察发现,快速排序和「二分查找」的原理类似,都是以对数阶的时间复杂度来缩小处理区间。</p>
<p><img alt="快速排序流程" src="../quick_sort.assets/quick_sort_overview.png" /></p>
<p align="center"> Fig. 快速排序流程 </p>
@ -2196,21 +2195,22 @@
</div>
</div>
<h2 id="1142">11.4.2. &nbsp; 算法特性<a class="headerlink" href="#1142" title="Permanent link">&para;</a></h2>
<p><strong>时间复杂度 <span class="arithmatex">\(O(n \log n)\)</span></strong> :平均情况下,哨兵划分的递归层数为 <span class="arithmatex">\(\log n\)</span> ,每层中的总循环数为 <span class="arithmatex">\(n\)</span> ,总体使用 <span class="arithmatex">\(O(n \log n)\)</span> 时间。</p>
<p>最差情况下,每轮哨兵划分操作都将长度为 <span class="arithmatex">\(n\)</span> 的数组划分为长度为 <span class="arithmatex">\(0\)</span><span class="arithmatex">\(n - 1\)</span> 的两个子数组,此时递归层数达到 <span class="arithmatex">\(n\)</span> 层,每层中的循环数为 <span class="arithmatex">\(n\)</span> ,总体使用 <span class="arithmatex">\(O(n^2)\)</span> 时间因此是“非稳定排序”。</p>
<p><strong>空间复杂度 <span class="arithmatex">\(O(n)\)</span></strong> :输入数组完全倒序下,达到最差递归深度 <span class="arithmatex">\(n\)</span> 。由于未借助辅助数组空间,因此是“原地排序”。</p>
<p><strong>非稳定排序</strong>哨兵划分最后一步可能会将基准数交换至相等元素的右</p>
<p><strong>时间复杂度 <span class="arithmatex">\(O(n \log n)\)</span></strong> 平均情况下,哨兵划分的递归层数为 <span class="arithmatex">\(\log n\)</span> ,每层中的总循环数为 <span class="arithmatex">\(n\)</span> ,总体使用 <span class="arithmatex">\(O(n \log n)\)</span> 时间。</p>
<p>最差情况下,每轮哨兵划分操作都将长度为 <span class="arithmatex">\(n\)</span> 的数组划分为长度为 <span class="arithmatex">\(0\)</span><span class="arithmatex">\(n - 1\)</span> 的两个子数组,此时递归层数达到 <span class="arithmatex">\(n\)</span> 层,每层中的循环数为 <span class="arithmatex">\(n\)</span> ,总体使用 <span class="arithmatex">\(O(n^2)\)</span> 时间因此快速排序是“自适应排序”。</p>
<p><strong>空间复杂度 <span class="arithmatex">\(O(n)\)</span></strong> 输入数组完全倒序的情况下,达到最差递归深度 <span class="arithmatex">\(n\)</span> 。由于未使用辅助数组,因此算法是“原地排序”。</p>
<p>哨兵划分最后一步,基准数可能会被交换至相等元素的右侧,因此是“非稳定排序”</p>
<h2 id="1143">11.4.3. &nbsp; 快排为什么快?<a class="headerlink" href="#1143" title="Permanent link">&para;</a></h2>
<p>命名能够看出,快速排序在效率方面一定“有两把刷子”。快速排序的平均时间复杂度虽然与「归并排序」和「堆排序」一致,但实际 <strong>效率更高</strong>,这是因为</p>
<p>名称上就能看出,快速排序在效率方面应该具有一定的优势。尽管快速排序的平均时间复杂度与「归并排序」和「堆排序」相同,但通常快速排序的效率更高,原因如下</p>
<ul>
<li><strong>出现最差情况的概率很低</strong>:虽然快速排序的最差时间复杂度为 <span class="arithmatex">\(O(n^2)\)</span> 不如归并排序,但绝大部分情况下,快速排序可以达到 <span class="arithmatex">\(O(n \log n)\)</span> 的复杂度。</li>
<li><strong>缓存使用效率高</strong>:哨兵划分操作时,将整个子数组加载缓存,访问元素效率高。而诸如「堆排序」需要跳跃式访问元素,因此不具有此特性。</li>
<li><strong>复杂度的常数系数低</strong>:在提及的三种算法中,快速排序的 <strong>比较</strong><strong>赋值</strong><strong>交换</strong> 三种操作的总数量最少(类似于「插入排序」快于「冒泡排序」的原因</li>
<li><strong>出现最差情况的概率很低</strong>:虽然快速排序的最差时间复杂度为 <span class="arithmatex">\(O(n^2)\)</span> 没有归并排序稳定,但绝大多数情况下,快速排序能在 <span class="arithmatex">\(O(n \log n)\)</span>时间复杂度下运行</li>
<li><strong>缓存使用效率高</strong>在执行哨兵划分操作时,系统可将整个子数组加载缓存,因此访问元素效率高。而「堆排序」这类算法需要跳跃式访问元素,从而缺乏这一特性。</li>
<li><strong>复杂度的常数系数低</strong>:在上述三种算法中,快速排序的比较、赋值、交换等操作的总数量最少。这与「插入排序」「冒泡排序」更快的原因类似</li>
</ul>
<h2 id="1144">11.4.4. &nbsp; 基准数优化<a class="headerlink" href="#1144" title="Permanent link">&para;</a></h2>
<p><strong>普通快速排序在某些输入下的时间效率变差</strong>。举个极端例子,假设输入数组是完全倒序的,由于我们选最左端元素为基准数,那么在哨兵划分完成后,基准数被交换至数组最右端,从而 <strong>左子数组长度为 <span class="arithmatex">\(n - 1\)</span>、右子数组长度为 <span class="arithmatex">\(0\)</span></strong> 。这样进一步递归下去,<strong>每轮哨兵划分后的右子数组长度都为 <span class="arithmatex">\(0\)</span></strong> ,分治策略失效,快速排序退化为「冒泡排序」</p>
<p>为了尽量避免这种情况发生,我们可以优化一下基准数的选取策略。首先,在哨兵划分中,我们可以 <strong>随机选取一个元素作为基准数</strong>。但如果运气很差,每次都选择到比较差的基准数,那么效率然不</p>
<p>进一步地,我们可以在数组中选取 3 个候选元素(一般为数组的首、尾、中点元素),<strong>并将三个候选元素的中位数作为基准数</strong>,这样基准数“既不大也不小”的概率就大大提升了。当然,如果数组很长的话,我们也可以选取更多候选元素,来进一步提升算法的稳健性。采取该方法后,时间复杂度劣化至 <span class="arithmatex">\(O(n^2)\)</span> 的概率极低</p>
<p><strong>快速排序在某些输入下的时间效率可能降低</strong>。举个极端例子,假设输入数组是完全倒序的,由于我们选最左端元素为基准数,那么在哨兵划分完成后,基准数被交换至数组最右端,导致左子数组长度为 <span class="arithmatex">\(n - 1\)</span> 、右子数组长度为 <span class="arithmatex">\(0\)</span> 。如此递归下去,每轮哨兵划分后的右子数组长度都为 <span class="arithmatex">\(0\)</span> ,分治策略失效,快速排序退化为「冒泡排序」。</p>
<p>为了尽量避免这种情况发生,<strong>我们可以优化哨兵划分中的基准数的选取策略</strong>。例如,我们可以随机选取一个元素作为基准数。然而,如果运气不佳,每次都选到不理想的基准数,效率然不尽如人意</p>
<p>需要注意的是,编程语言通常生成的是“伪随机数”。如果我们针对伪随机数序列构建一个特定的测试样例,那么快速排序的效率仍然可能劣化</p>
<p>为了进一步改进,我们可以在数组中选取三个候选元素(通常为数组的首、尾、中点元素),<strong>并将这三个候选元素的中位数作为基准数</strong>。这样一来,基准数“既不太小也不太大”的概率将大幅提升。当然,我们还可以选取更多候选元素,以进一步提高算法的稳健性。采用这种方法后,时间复杂度劣化至 <span class="arithmatex">\(O(n^2)\)</span> 的概率大大降低。</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-content">
<div class="tabbed-block">
@ -2511,8 +2511,8 @@
</div>
</div>
<h2 id="1145">11.4.5. &nbsp; 尾递归优化<a class="headerlink" href="#1145" title="Permanent link">&para;</a></h2>
<p><strong>普通快速排序在某些输入下的空间效率变差</strong>仍然以完全倒序的输入数组为例,由于每轮哨兵划分后右子数组长度为 0 ,那么将形成一个高度为 <span class="arithmatex">\(n - 1\)</span> 的递归树,此时使用的栈帧空间大小劣化至 <span class="arithmatex">\(O(n)\)</span></p>
<p>为了避免栈帧空间的累积,我们可以在每轮哨兵排序完成后,判断两个子数组的长度大小,仅递归排序较短的子数组。由于较短子数组长度不会超过 <span class="arithmatex">\(\frac{n}{2}\)</span> ,因此这样做能保证递归深度不超过 <span class="arithmatex">\(\log n\)</span> 最差空间复杂度优化至 <span class="arithmatex">\(O(\log n)\)</span></p>
<p><strong>在某些输入下,快速排序可能占用空间较多</strong>。以完全倒序的输入数组为例,由于每轮哨兵划分后右子数组长度为 <span class="arithmatex">\(0\)</span> ,递归树的高度会达到 <span class="arithmatex">\(n - 1\)</span> ,此时需要占用 <span class="arithmatex">\(O(n)\)</span> 大小的栈帧空间</p>
<p>为了防止栈帧空间的累积,我们可以在每轮哨兵排序完成后,比较两个子数组的长度<strong>仅对较短的子数组进行递归</strong>。由于较短子数组长度不会超过 <span class="arithmatex">\(\frac{n}{2}\)</span> ,因此这种方法能确保递归深度不超过 <span class="arithmatex">\(\log n\)</span> 从而将最差空间复杂度优化至 <span class="arithmatex">\(O(\log n)\)</span></p>
<div class="tabbed-set tabbed-alternate" data-tabs="5:10"><input checked="checked" id="__tabbed_5_1" name="__tabbed_5" type="radio" /><input id="__tabbed_5_2" name="__tabbed_5" type="radio" /><input id="__tabbed_5_3" name="__tabbed_5" type="radio" /><input id="__tabbed_5_4" name="__tabbed_5" type="radio" /><input id="__tabbed_5_5" name="__tabbed_5" type="radio" /><input id="__tabbed_5_6" name="__tabbed_5" type="radio" /><input id="__tabbed_5_7" name="__tabbed_5" type="radio" /><input id="__tabbed_5_8" name="__tabbed_5" type="radio" /><input id="__tabbed_5_9" name="__tabbed_5" type="radio" /><input id="__tabbed_5_10" name="__tabbed_5" type="radio" /><div class="tabbed-labels"><label for="__tabbed_5_1">Java</label><label for="__tabbed_5_2">C++</label><label for="__tabbed_5_3">Python</label><label for="__tabbed_5_4">Go</label><label for="__tabbed_5_5">JavaScript</label><label for="__tabbed_5_6">TypeScript</label><label for="__tabbed_5_7">C</label><label for="__tabbed_5_8">C#</label><label for="__tabbed_5_9">Swift</label><label for="__tabbed_5_10">Zig</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
@ -2701,9 +2701,9 @@
<div class="admonition question">
<p class="admonition-title">哨兵划分中“从右往左查找”与“从左往右查找”的顺序可以交换吗?</p>
<p>不行,当我们以最左端元素为基准数时,必须先“从右往左查找”再“从左往右查找”。这个结论有些反直觉,我们来剖析一下原因。</p>
<p>哨兵划分 <code>partition()</code> 的最后一步是交换 <code>nums[left]</code><code>nums[i]</code> 完成交换后,基准数左边的元素都 <code>&lt;=</code> 基准数,<strong>这就要求最后一步交换前 <code>nums[left] &gt;= nums[i]</code> 必须成立</strong>。假设我们先“从左往右查找”,那么如果找不到比基准数更小的元素,<strong>则会在 <code>i == j</code> 时跳出循环,此时可能 <code>nums[j] == nums[i] &gt; nums[left]</code></strong> 也就是说,此时最后一步交换操作会把一个比基准数更大的元素交换至数组最左端,导致哨兵划分失败。</p>
<p>举个例子,给定数组 <code>[0, 0, 0, 0, 1]</code> ,如果先“从左向右查找”,哨兵划分后数组为 <code>[1, 0, 0, 0, 0]</code> ,这个结果是不的。</p>
<p>再深想一步,如果我们选择 <code>nums[right]</code> 为基准数,那么正好反过来,必须先“从左往右查找”。</p>
<p>哨兵划分 <code>partition()</code> 的最后一步是交换 <code>nums[left]</code><code>nums[i]</code> 完成交换后,基准数左边的元素都 <code>&lt;=</code> 基准数,<strong>这就要求最后一步交换前 <code>nums[left] &gt;= nums[i]</code> 必须成立</strong>。假设我们先“从左往右查找”,那么如果找不到比基准数更小的元素,<strong>则会在 <code>i == j</code> 时跳出循环,此时可能 <code>nums[j] == nums[i] &gt; nums[left]</code></strong>也就是说,此时最后一步交换操作会把一个比基准数更大的元素交换至数组最左端,导致哨兵划分失败。</p>
<p>举个例子,给定数组 <code>[0, 0, 0, 0, 1]</code> ,如果先“从左向右查找”,哨兵划分后数组为 <code>[1, 0, 0, 0, 0]</code> ,这个结果是不正确的。</p>
<p>再深入思考一下,如果我们选择 <code>nums[right]</code> 为基准数,那么正好反过来,必须先“从左往右查找”。</p>
</div>

View File

@ -1740,24 +1740,24 @@
<h1 id="118">11.8. &nbsp; 基数排序<a class="headerlink" href="#118" title="Permanent link">&para;</a></h1>
<p>介绍计数排序适用于数据量 <span class="arithmatex">\(n\)</span> 大但数据范围 <span class="arithmatex">\(m\)</span> 不大的情况。假设需要排序 <span class="arithmatex">\(n = 10^6\)</span> 个学号数据,学号是 <span class="arithmatex">\(8\)</span> 位数字,那么数据范围 <span class="arithmatex">\(m = 10^8\)</span> 大,使用计数排序需要开辟巨大的内存空间,而基数排序可以避免这种情况。</p>
<p>「基数排序 Radix Sort」主体思路与计数排序一致,也通过统计出现次数实现排序,<strong>并在此基础上利用位与位之间的递进关系,依次对每一位行排序</strong>,从而获得排序结果。</p>
<p>一节我们介绍计数排序,它适用于数据量 <span class="arithmatex">\(n\)</span> 大但数据范围 <span class="arithmatex">\(m\)</span> 较小的情况。假设我们需要对 <span class="arithmatex">\(n = 10^6\)</span> 个学号进行排序,而学号是一个 <span class="arithmatex">\(8\)</span> 位数字,这意味着数据范围 <span class="arithmatex">\(m = 10^8\)</span> 非常大,使用计数排序需要分配大量内存空间,而基数排序可以避免这种情况。</p>
<p>「基数排序 Radix Sort」的核心思想与计数排序一致,也通过统计个数来实现排序。在此基础上,基数排序利用数字各位之间的递进关系,依次对每一位行排序**,从而得到最终的排序结果。</p>
<h2 id="1181">11.8.1. &nbsp; 算法流程<a class="headerlink" href="#1181" title="Permanent link">&para;</a></h2>
<p>上述的学号数据为例,设数字最低位<span class="arithmatex">\(1\)</span>最高位<span class="arithmatex">\(8\)</span> 位,基数排序的流程为</p>
<p>以学号数据为例,设数字最低位<span class="arithmatex">\(1\)</span>最高位<span class="arithmatex">\(8\)</span> 位,基数排序的步骤如下</p>
<ol>
<li>初始化位数 <span class="arithmatex">\(k = 1\)</span> </li>
<li>对学号的第 <span class="arithmatex">\(k\)</span> 位执行「计数排序」完成后,数据即按照<span class="arithmatex">\(k\)</span> 位从小到大排序;</li>
<li><span class="arithmatex">\(k\)</span> <span class="arithmatex">\(1\)</span> 并返回第 <code>2.</code> 继续迭代,直至排序完所有位后结束;</li>
<li>对学号的第 <span class="arithmatex">\(k\)</span> 位执行「计数排序」完成后,数据会根据<span class="arithmatex">\(k\)</span> 位从小到大排序;</li>
<li><span class="arithmatex">\(k\)</span> <span class="arithmatex">\(1\)</span> 然后返回步骤 <code>2.</code> 继续迭代,直到所有位都排序完成后结束;</li>
</ol>
<p><img alt="基数排序算法流程" src="../radix_sort.assets/radix_sort_overview.png" /></p>
<p align="center"> Fig. 基数排序算法流程 </p>
<p>下面来剖析代码实现。对于一个 <span class="arithmatex">\(d\)</span> 进制的数字 <span class="arithmatex">\(x\)</span> ,其第 <span class="arithmatex">\(k\)</span><span class="arithmatex">\(x_k\)</span> 计算公式</p>
<p>下面来剖析代码实现。对于一个 <span class="arithmatex">\(d\)</span> 进制的数字 <span class="arithmatex">\(x\)</span> 要获取其第 <span class="arithmatex">\(k\)</span><span class="arithmatex">\(x_k\)</span> ,可以使用以下计算公式</p>
<div class="arithmatex">\[
x_k = \lfloor\frac{x}{d^{k-1}}\rfloor \mod d
\]</div>
<p>其中 <span class="arithmatex">\(\lfloor a \rfloor\)</span> 表对浮点数 <span class="arithmatex">\(a\)</span> 执行向下取整,<span class="arithmatex">\(\mod d\)</span> 表对 <span class="arithmatex">\(d\)</span> 取余。学号数据<span class="arithmatex">\(d = 10\)</span> , <span class="arithmatex">\(k \in [1, 8]\)</span></p>
<p>此外,我们需要小幅改动计数排序代码,使之可以根据数字第 <span class="arithmatex">\(k\)</span>行排序。</p>
<p>其中 <span class="arithmatex">\(\lfloor a \rfloor\)</span>对浮点数 <span class="arithmatex">\(a\)</span> 向下取整,<span class="arithmatex">\(\mod d\)</span><span class="arithmatex">\(d\)</span> 取余。对于学号数据<span class="arithmatex">\(d = 10\)</span> <span class="arithmatex">\(k \in [1, 8]\)</span></p>
<p>此外,我们需要小幅改动计数排序代码,使之可以根据数字<span class="arithmatex">\(k\)</span>行排序。</p>
<div class="tabbed-set tabbed-alternate" data-tabs="1:10"><input checked="checked" id="__tabbed_1_1" name="__tabbed_1" type="radio" /><input id="__tabbed_1_2" name="__tabbed_1" type="radio" /><input id="__tabbed_1_3" name="__tabbed_1" type="radio" /><input id="__tabbed_1_4" name="__tabbed_1" type="radio" /><input id="__tabbed_1_5" name="__tabbed_1" type="radio" /><input id="__tabbed_1_6" name="__tabbed_1" type="radio" /><input id="__tabbed_1_7" name="__tabbed_1" type="radio" /><input id="__tabbed_1_8" name="__tabbed_1" type="radio" /><input id="__tabbed_1_9" name="__tabbed_1" type="radio" /><input id="__tabbed_1_10" name="__tabbed_1" type="radio" /><div class="tabbed-labels"><label for="__tabbed_1_1">Java</label><label for="__tabbed_1_2">C++</label><label for="__tabbed_1_3">Python</label><label for="__tabbed_1_4">Go</label><label for="__tabbed_1_5">JavaScript</label><label for="__tabbed_1_6">TypeScript</label><label for="__tabbed_1_7">C</label><label for="__tabbed_1_8">C#</label><label for="__tabbed_1_9">Swift</label><label for="__tabbed_1_10">Zig</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
@ -2204,12 +2204,12 @@ x_k = \lfloor\frac{x}{d^{k-1}}\rfloor \mod d
</div>
<div class="admonition question">
<p class="admonition-title">为什么从最低位开始排序?</p>
<p>对于先后两轮排序,第二轮排序可能会覆盖一轮排序的结果,比如第一轮认为 <span class="arithmatex">\(a &lt; b\)</span> ,而第二轮认为 <span class="arithmatex">\(a &gt; b\)</span> 第二轮取代第一轮的结果。由于数字高位比低位的优先级更高,所以要先排序低位再排序高位。</p>
<p>在连续的排序轮次中,后一轮排序会覆盖一轮排序的结果。举例来说,如果第一轮排序结果 <span class="arithmatex">\(a &lt; b\)</span> ,而第二轮排序结果 <span class="arithmatex">\(a &gt; b\)</span> 那么第二轮的结果将取代第一轮的结果。由于数字高位优先级高于低位,我们应该先排序低位再排序高位。</p>
</div>
<h2 id="1182">11.8.2. &nbsp; 算法特性<a class="headerlink" href="#1182" title="Permanent link">&para;</a></h2>
<p><strong>时间复杂度 <span class="arithmatex">\(O(n k)\)</span></strong> :设数据量为 <span class="arithmatex">\(n\)</span> 、数据为 <span class="arithmatex">\(d\)</span> 进制、最大为 <span class="arithmatex">\(k\)</span> ,则对某一位执行计数排序使用 <span class="arithmatex">\(O(n + d)\)</span> 时间,排序 <span class="arithmatex">\(k\)</span> 位使用 <span class="arithmatex">\(O((n + d)k)\)</span> 时间;一般情况下 <span class="arithmatex">\(d\)</span><span class="arithmatex">\(k\)</span>较小,此时时间复杂度近似为 <span class="arithmatex">\(O(n)\)</span></p>
<p><strong>空间复杂度 <span class="arithmatex">\(O(n + d)\)</span></strong> :与计数排序一样,借助长度分别<span class="arithmatex">\(n\)</span> , <span class="arithmatex">\(d\)</span> 的数组 <code>res</code><code>counter</code> ,因此“非原地排序”。</p>
<p>与计数排序一致,基数排序也是稳定排序。相于计数排序,基数排序适用于数值范围较大的情况,<strong>但前提是数据必须可以表示为固定位数的格式,且位数不能</strong>比如浮点数不适合使用基数排序,因为其位数 <span class="arithmatex">\(k\)</span> 大,可能时间复杂度 <span class="arithmatex">\(O(nk) \gg O(n^2)\)</span></p>
<p><strong>时间复杂度 <span class="arithmatex">\(O(nk)\)</span></strong> :设数据量为 <span class="arithmatex">\(n\)</span> 、数据为 <span class="arithmatex">\(d\)</span> 进制、最大位数<span class="arithmatex">\(k\)</span> ,则对某一位执行计数排序使用 <span class="arithmatex">\(O(n + d)\)</span> 时间,排序所有 <span class="arithmatex">\(k\)</span> 位使用 <span class="arithmatex">\(O((n + d)k)\)</span> 时间。通常情况下<span class="arithmatex">\(d\)</span><span class="arithmatex">\(k\)</span>相对较小,时间复杂度趋向 <span class="arithmatex">\(O(n)\)</span></p>
<p><strong>空间复杂度 <span class="arithmatex">\(O(n + d)\)</span></strong> :与计数排序相同,基数排序需要借助长度为 <span class="arithmatex">\(n\)</span> <span class="arithmatex">\(d\)</span> 的数组 <code>res</code><code>counter</code> ,因此它是一种“非原地排序”。</p>
<p>基数排序与计数排序一样,都属于稳定排序。相于计数排序,基数排序适用于数值范围较大的情况,<strong>但前提是数据必须可以表示为固定位数的格式,且位数不能</strong>例如,浮点数不适合使用基数排序,因为其位数 <span class="arithmatex">\(k\)</span> 大,可能导致时间复杂度 <span class="arithmatex">\(O(nk) \gg O(n^2)\)</span></p>

View File

@ -1681,19 +1681,20 @@
<h1 id="119">11.9. &nbsp; 小结<a class="headerlink" href="#119" title="Permanent link">&para;</a></h1>
<ul>
<li>冒泡排序通过交换相邻元素来实现排序。通过增加标志位实现提前返回,我们可将冒泡排序的最佳时间复杂度优化 <span class="arithmatex">\(O(N)\)</span></li>
<li>插入排序每轮将待排序区间内元素插入已排序区间的正确位置,从而实现排序。插入排序的时间复杂度<span class="arithmatex">\(O(N^2)\)</span> ,但因为总体操作少而很受欢迎,一般用于小数据量的排序工作</li>
<li>快速排序基于哨兵划分操作实现排序。在哨兵划分中,有可能每次都选取到最差的基准数,从而导致时间复杂度劣化至 <span class="arithmatex">\(O(N^2)\)</span> ,通过引入中位数基准数或随机基准数可大大降低劣化概率。尾递归方法可以有效减小递归深度,将空间复杂度优化 <span class="arithmatex">\(O(\log N)\)</span></li>
<li>归并排序包划分和合并两个阶段,是分而治之的标准体现。对于归并排序,排序数组需要借助辅助数组,空间复杂度为 <span class="arithmatex">\(O(N)\)</span> ;而排序链表的空间复杂度可以优化至 <span class="arithmatex">\(O(1)\)</span></li>
<li>桶排序分为三步,数据分桶、桶内排序合并结果体现分治策略,适用于体量很大的数据。桶排序的难点在于数据的平均划分</li>
<li>计数排序是桶排序的一特例,通过统计数据出现次数来实现排序适用于数据量大但数据范围不大的情况,并且要求数据可以被转化为正整数。</li>
<li>基数排序通过依次排序各位来实现数据排序,要求数据可以被表示为固定位数的数字。</li>
<li>冒泡排序通过交换相邻元素来实现排序。通过添加一个标志位实现提前返回,我们可将冒泡排序的最佳时间复杂度优化 <span class="arithmatex">\(O(n)\)</span></li>
<li>插入排序每轮将待排序区间内元素插入已排序区间的正确位置,从而完成排序。虽然插入排序的时间复杂度为 <span class="arithmatex">\(O(n^2)\)</span> ,但由于单元操作相对较少,它在小数据量的排序任务中非常受欢迎</li>
<li>快速排序基于哨兵划分操作实现排序。在哨兵划分中,有可能每次都选取到最差的基准数,导致时间复杂度劣化至 <span class="arithmatex">\(O(n^2)\)</span> 引入中位数基准数或随机基准数可以降低这种劣化概率。尾递归方法可以有效地减少递归深度,将空间复杂度优化 <span class="arithmatex">\(O(\log n)\)</span></li>
<li>归并排序包划分和合并两个阶段,典型地体现了分治策略。在归并排序,排序数组需要创建辅助数组,空间复杂度为 <span class="arithmatex">\(O(n)\)</span> 而排序链表的空间复杂度可以优化至 <span class="arithmatex">\(O(1)\)</span></li>
<li>桶排序包含三个步骤:数据分桶、桶内排序合并结果。它同样体现分治策略,适用于数据体量很大的情况。桶排序的关键在于数据进行平均分配</li>
<li>计数排序是桶排序的一特例,通过统计数据出现次数来实现排序。计数排序适用于数据量大但数据范围有限的情况,并且要求数据能够转换为正整数。</li>
<li>基数排序通过逐位排序来实现数据排序,要求数据能够表示为固定位数的数字。</li>
</ul>
<p><img alt="排序算法对比" src="../summary.assets/sorting_algorithms_comparison.png" /></p>
<p align="center"> Fig. 排序算法对比 </p>
<ul>
<li>总体来看,我们追求运行快、稳定、原地、正向自适应性的排序。显然,如同其数据结构与算法一样,同时满足这些条件的排序算法并不存在,我们需要根据问题特点来选择排序算法。</li>
<li>总体来看,我们追求运行快、稳定、原地、正向自适应性的排序。显然,如同其数据结构与算法一样,同时满足这些条件的排序算法并不存在,我们需要根据问题特点来选择排序算法。</li>
<li>总的来说,我们希望找到一种排序算法,具有高效率、稳定、原地以及正向自适应性等优点。然而,正如其他数据结构和算法一样,没有一种排序算法能够同时满足所有这些条件。在实际应用中,我们需要根据数据的特性来选择合适的排序算法。</li>
</ul>

File diff suppressed because one or more lines are too long

View File

@ -2,272 +2,272 @@
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://www.hello-algo.com/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_appendix/contribution/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_appendix/installation/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_array_and_linkedlist/array/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_array_and_linkedlist/linked_list/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_array_and_linkedlist/list/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_array_and_linkedlist/summary/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_computational_complexity/performance_evaluation/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_computational_complexity/space_complexity/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_computational_complexity/space_time_tradeoff/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_computational_complexity/summary/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_computational_complexity/time_complexity/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_data_structure/classification_of_data_structure/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_data_structure/data_and_memory/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_data_structure/summary/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_graph/graph/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_graph/graph_operations/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_graph/graph_traversal/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_graph/summary/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_hashing/hash_collision/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_hashing/hash_map/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_hashing/summary/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_heap/build_heap/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_heap/heap/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_heap/summary/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_introduction/algorithms_are_everywhere/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_introduction/summary/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_introduction/what_is_dsa/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_preface/about_the_book/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_preface/suggestions/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_preface/summary/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_reference/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_searching/binary_search/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_searching/hashing_search/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_searching/linear_search/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_searching/summary/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_sorting/bubble_sort/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_sorting/bucket_sort/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_sorting/counting_sort/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_sorting/insertion_sort/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_sorting/intro_to_sort/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_sorting/merge_sort/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_sorting/quick_sort/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_sorting/radix_sort/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_sorting/summary/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_stack_and_queue/deque/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_stack_and_queue/queue/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_stack_and_queue/stack/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_stack_and_queue/summary/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_tree/avl_tree/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_tree/binary_search_tree/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_tree/binary_tree/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_tree/binary_tree_traversal/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://www.hello-algo.com/chapter_tree/summary/</loc>
<lastmod>2023-04-08</lastmod>
<lastmod>2023-04-09</lastmod>
<changefreq>daily</changefreq>
</url>
</urlset>

Binary file not shown.