mirror of
https://github.com/krahets/hello-algo.git
synced 2026-07-09 22:16:06 +00:00
deploy
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
<link rel="canonical" href="https://www.hello-algo.com/chapter_computational_complexity/time_complexity/">
|
||||
|
||||
|
||||
<link rel="prev" href="../performance_evaluation/">
|
||||
<link rel="prev" href="../iteration_and_recursion/">
|
||||
|
||||
|
||||
<link rel="next" href="../space_complexity/">
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
|
||||
|
||||
<title>2.2 时间复杂度 - Hello 算法</title>
|
||||
<title>2.3 时间复杂度 - Hello 算法</title>
|
||||
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
<div data-md-component="skip">
|
||||
|
||||
|
||||
<a href="#22" class="md-skip">
|
||||
<a href="#23" class="md-skip">
|
||||
跳转至
|
||||
</a>
|
||||
|
||||
@@ -117,7 +117,7 @@
|
||||
<div class="md-header__topic" data-md-component="header-topic">
|
||||
<span class="md-ellipsis">
|
||||
|
||||
2.2 时间复杂度
|
||||
2.3 时间复杂度
|
||||
|
||||
</span>
|
||||
</div>
|
||||
@@ -525,6 +525,8 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -592,6 +594,26 @@
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../iteration_and_recursion/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
2.2 迭代与递归
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -607,7 +629,7 @@
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
2.2 时间复杂度
|
||||
2.3 时间复杂度
|
||||
</span>
|
||||
|
||||
|
||||
@@ -618,7 +640,7 @@
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
2.2 时间复杂度
|
||||
2.3 时间复杂度
|
||||
</span>
|
||||
|
||||
|
||||
@@ -640,25 +662,25 @@
|
||||
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#221" class="md-nav__link">
|
||||
2.2.1 统计时间增长趋势
|
||||
<a href="#231" class="md-nav__link">
|
||||
2.3.1 统计时间增长趋势
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#222" class="md-nav__link">
|
||||
2.2.2 函数渐近上界
|
||||
<a href="#232" class="md-nav__link">
|
||||
2.3.2 函数渐近上界
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#223" class="md-nav__link">
|
||||
2.2.3 推算方法
|
||||
<a href="#233" class="md-nav__link">
|
||||
2.3.3 推算方法
|
||||
</a>
|
||||
|
||||
<nav class="md-nav" aria-label="2.2.3 推算方法">
|
||||
<nav class="md-nav" aria-label="2.3.3 推算方法">
|
||||
<ul class="md-nav__list">
|
||||
|
||||
<li class="md-nav__item">
|
||||
@@ -681,11 +703,11 @@
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#224" class="md-nav__link">
|
||||
2.2.4 常见类型
|
||||
<a href="#234" class="md-nav__link">
|
||||
2.3.4 常见类型
|
||||
</a>
|
||||
|
||||
<nav class="md-nav" aria-label="2.2.4 常见类型">
|
||||
<nav class="md-nav" aria-label="2.3.4 常见类型">
|
||||
<ul class="md-nav__list">
|
||||
|
||||
<li class="md-nav__item">
|
||||
@@ -743,8 +765,8 @@
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#225" class="md-nav__link">
|
||||
2.2.5 最差、最佳、平均时间复杂度
|
||||
<a href="#235" class="md-nav__link">
|
||||
2.3.5 最差、最佳、平均时间复杂度
|
||||
</a>
|
||||
|
||||
</li>
|
||||
@@ -768,7 +790,7 @@
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
2.3 空间复杂度
|
||||
2.4 空间复杂度
|
||||
</span>
|
||||
|
||||
|
||||
@@ -788,7 +810,7 @@
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
2.4 小结
|
||||
2.5 小结
|
||||
</span>
|
||||
|
||||
|
||||
@@ -3471,25 +3493,25 @@
|
||||
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#221" class="md-nav__link">
|
||||
2.2.1 统计时间增长趋势
|
||||
<a href="#231" class="md-nav__link">
|
||||
2.3.1 统计时间增长趋势
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#222" class="md-nav__link">
|
||||
2.2.2 函数渐近上界
|
||||
<a href="#232" class="md-nav__link">
|
||||
2.3.2 函数渐近上界
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#223" class="md-nav__link">
|
||||
2.2.3 推算方法
|
||||
<a href="#233" class="md-nav__link">
|
||||
2.3.3 推算方法
|
||||
</a>
|
||||
|
||||
<nav class="md-nav" aria-label="2.2.3 推算方法">
|
||||
<nav class="md-nav" aria-label="2.3.3 推算方法">
|
||||
<ul class="md-nav__list">
|
||||
|
||||
<li class="md-nav__item">
|
||||
@@ -3512,11 +3534,11 @@
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#224" class="md-nav__link">
|
||||
2.2.4 常见类型
|
||||
<a href="#234" class="md-nav__link">
|
||||
2.3.4 常见类型
|
||||
</a>
|
||||
|
||||
<nav class="md-nav" aria-label="2.2.4 常见类型">
|
||||
<nav class="md-nav" aria-label="2.3.4 常见类型">
|
||||
<ul class="md-nav__list">
|
||||
|
||||
<li class="md-nav__item">
|
||||
@@ -3574,8 +3596,8 @@
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#225" class="md-nav__link">
|
||||
2.2.5 最差、最佳、平均时间复杂度
|
||||
<a href="#235" class="md-nav__link">
|
||||
2.3.5 最差、最佳、平均时间复杂度
|
||||
</a>
|
||||
|
||||
</li>
|
||||
@@ -3603,7 +3625,7 @@
|
||||
|
||||
|
||||
|
||||
<h1 id="22">2.2 时间复杂度<a class="headerlink" href="#22" title="Permanent link">¶</a></h1>
|
||||
<h1 id="23">2.3 时间复杂度<a class="headerlink" href="#23" title="Permanent link">¶</a></h1>
|
||||
<p>运行时间可以直观且准确地反映算法的效率。如果我们想要准确预估一段代码的运行时间,应该如何操作呢?</p>
|
||||
<ol>
|
||||
<li><strong>确定运行平台</strong>,包括硬件配置、编程语言、系统环境等,这些因素都会影响代码的运行效率。</li>
|
||||
@@ -3765,7 +3787,7 @@
|
||||
1 + 1 + 10 + (1 + 5) \times n = 6n + 12
|
||||
\]</div>
|
||||
<p>但实际上,<strong>统计算法的运行时间既不合理也不现实</strong>。首先,我们不希望将预估时间和运行平台绑定,因为算法需要在各种不同的平台上运行。其次,我们很难获知每种操作的运行时间,这给预估过程带来了极大的难度。</p>
|
||||
<h2 id="221">2.2.1 统计时间增长趋势<a class="headerlink" href="#221" title="Permanent link">¶</a></h2>
|
||||
<h2 id="231">2.3.1 统计时间增长趋势<a class="headerlink" href="#231" title="Permanent link">¶</a></h2>
|
||||
<p>时间复杂度分析统计的不是算法运行时间,<strong>而是算法运行时间随着数据量变大时的增长趋势</strong>。</p>
|
||||
<p>“时间增长趋势”这个概念比较抽象,我们通过一个例子来加以理解。假设输入数据大小为 <span class="arithmatex">\(n\)</span> ,给定三个算法函数 <code>A</code> 、 <code>B</code> 和 <code>C</code> :</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="2:12"><input checked="checked" id="__tabbed_2_1" name="__tabbed_2" type="radio" /><input id="__tabbed_2_2" name="__tabbed_2" type="radio" /><input id="__tabbed_2_3" name="__tabbed_2" type="radio" /><input id="__tabbed_2_4" name="__tabbed_2" type="radio" /><input id="__tabbed_2_5" name="__tabbed_2" type="radio" /><input id="__tabbed_2_6" name="__tabbed_2" type="radio" /><input id="__tabbed_2_7" name="__tabbed_2" type="radio" /><input id="__tabbed_2_8" name="__tabbed_2" type="radio" /><input id="__tabbed_2_9" name="__tabbed_2" type="radio" /><input id="__tabbed_2_10" name="__tabbed_2" type="radio" /><input id="__tabbed_2_11" name="__tabbed_2" type="radio" /><input id="__tabbed_2_12" name="__tabbed_2" type="radio" /><div class="tabbed-labels"><label for="__tabbed_2_1">Java</label><label for="__tabbed_2_2">C++</label><label for="__tabbed_2_3">Python</label><label for="__tabbed_2_4">Go</label><label for="__tabbed_2_5">JS</label><label for="__tabbed_2_6">TS</label><label for="__tabbed_2_7">C</label><label for="__tabbed_2_8">C#</label><label for="__tabbed_2_9">Swift</label><label for="__tabbed_2_10">Zig</label><label for="__tabbed_2_11">Dart</label><label for="__tabbed_2_12">Rust</label></div>
|
||||
@@ -3982,14 +4004,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>图 2-1 展示了以上三个算法函数的时间复杂度。</p>
|
||||
<p>图 2-7 展示了以上三个算法函数的时间复杂度。</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>
|
||||
<li>算法 <code>C</code> 中的打印操作需要循环 <span class="arithmatex">\(1000000\)</span> 次,虽然运行时间很长,但它与输入数据大小 <span class="arithmatex">\(n\)</span> 无关。因此 <code>C</code> 的时间复杂度和 <code>A</code> 相同,仍为“常数阶”。</li>
|
||||
</ul>
|
||||
<p><img alt="算法 A 、B 和 C 的时间增长趋势" src="../time_complexity.assets/time_complexity_simple_example.png" /></p>
|
||||
<p align="center"> 图 2-1 算法 A 、B 和 C 的时间增长趋势 </p>
|
||||
<p align="center"> 图 2-7 算法 A 、B 和 C 的时间增长趋势 </p>
|
||||
|
||||
<p>相较于直接统计算法运行时间,时间复杂度分析有哪些特点呢?</p>
|
||||
<ul>
|
||||
@@ -3997,7 +4019,7 @@
|
||||
<li><strong>时间复杂度的推算方法更简便</strong>。显然,运行平台和计算操作类型都与算法运行时间的增长趋势无关。因此在时间复杂度分析中,我们可以简单地将所有计算操作的执行时间视为相同的“单位时间”,从而将“计算操作的运行时间的统计”简化为“计算操作的数量的统计”,这样以来估算难度就大大降低了。</li>
|
||||
<li><strong>时间复杂度也存在一定的局限性</strong>。例如,尽管算法 <code>A</code> 和 <code>C</code> 的时间复杂度相同,但实际运行时间差别很大。同样,尽管算法 <code>B</code> 的时间复杂度比 <code>C</code> 高,但在输入数据大小 <span class="arithmatex">\(n\)</span> 较小时,算法 <code>B</code> 明显优于算法 <code>C</code> 。在这些情况下,我们很难仅凭时间复杂度判断算法效率的高低。当然,尽管存在上述问题,复杂度分析仍然是评判算法效率最有效且常用的方法。</li>
|
||||
</ul>
|
||||
<h2 id="222">2.2.2 函数渐近上界<a class="headerlink" href="#222" title="Permanent link">¶</a></h2>
|
||||
<h2 id="232">2.3.2 函数渐近上界<a class="headerlink" href="#232" title="Permanent link">¶</a></h2>
|
||||
<p>给定一个输入大小为 <span class="arithmatex">\(n\)</span> 的函数:</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="3:12"><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" /><input id="__tabbed_3_11" name="__tabbed_3" type="radio" /><input id="__tabbed_3_12" 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">JS</label><label for="__tabbed_3_6">TS</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><label for="__tabbed_3_11">Dart</label><label for="__tabbed_3_12">Rust</label></div>
|
||||
<div class="tabbed-content">
|
||||
@@ -4156,11 +4178,11 @@ $$
|
||||
T(n) = O(f(n))
|
||||
$$</p>
|
||||
</div>
|
||||
<p>如图 2-2 所示,计算渐近上界就是寻找一个函数 <span class="arithmatex">\(f(n)\)</span> ,使得当 <span class="arithmatex">\(n\)</span> 趋向于无穷大时,<span class="arithmatex">\(T(n)\)</span> 和 <span class="arithmatex">\(f(n)\)</span> 处于相同的增长级别,仅相差一个常数项 <span class="arithmatex">\(c\)</span> 的倍数。</p>
|
||||
<p>如图 2-8 所示,计算渐近上界就是寻找一个函数 <span class="arithmatex">\(f(n)\)</span> ,使得当 <span class="arithmatex">\(n\)</span> 趋向于无穷大时,<span class="arithmatex">\(T(n)\)</span> 和 <span class="arithmatex">\(f(n)\)</span> 处于相同的增长级别,仅相差一个常数项 <span class="arithmatex">\(c\)</span> 的倍数。</p>
|
||||
<p><img alt="函数的渐近上界" src="../time_complexity.assets/asymptotic_upper_bound.png" /></p>
|
||||
<p align="center"> 图 2-2 函数的渐近上界 </p>
|
||||
<p align="center"> 图 2-8 函数的渐近上界 </p>
|
||||
|
||||
<h2 id="223">2.2.3 推算方法<a class="headerlink" href="#223" title="Permanent link">¶</a></h2>
|
||||
<h2 id="233">2.3.3 推算方法<a class="headerlink" href="#233" title="Permanent link">¶</a></h2>
|
||||
<p>渐近上界的数学味儿有点重,如果你感觉没有完全理解,也无须担心。因为在实际使用中,我们只需要掌握推算方法,数学意义就可以逐渐领悟。</p>
|
||||
<p>根据定义,确定 <span class="arithmatex">\(f(n)\)</span> 之后,我们便可得到时间复杂度 <span class="arithmatex">\(O(f(n))\)</span> 。那么如何确定渐近上界 <span class="arithmatex">\(f(n)\)</span> 呢?总体分为两步:首先统计操作数量,然后判断渐近上界。</p>
|
||||
<h3 id="1">1. 第一步:统计操作数量<a class="headerlink" href="#1" title="Permanent link">¶</a></h3>
|
||||
@@ -4408,8 +4430,8 @@ T(n) & = n^2 + n & \text{偷懒统计 (o.O)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<h2 id="224">2.2.4 常见类型<a class="headerlink" href="#224" title="Permanent link">¶</a></h2>
|
||||
<p>设输入数据大小为 <span class="arithmatex">\(n\)</span> ,常见的时间复杂度类型如图 2-3 所示(按照从低到高的顺序排列)。</p>
|
||||
<h2 id="234">2.3.4 常见类型<a class="headerlink" href="#234" title="Permanent link">¶</a></h2>
|
||||
<p>设输入数据大小为 <span class="arithmatex">\(n\)</span> ,常见的时间复杂度类型如图 2-9 所示(按照从低到高的顺序排列)。</p>
|
||||
<div class="arithmatex">\[
|
||||
\begin{aligned}
|
||||
O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!) \newline
|
||||
@@ -4417,7 +4439,7 @@ O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!
|
||||
\end{aligned}
|
||||
\]</div>
|
||||
<p><img alt="常见的时间复杂度类型" src="../time_complexity.assets/time_complexity_common_types.png" /></p>
|
||||
<p align="center"> 图 2-3 常见的时间复杂度类型 </p>
|
||||
<p align="center"> 图 2-9 常见的时间复杂度类型 </p>
|
||||
|
||||
<div class="admonition tip">
|
||||
<p class="admonition-title">Tip</p>
|
||||
@@ -5020,9 +5042,9 @@ O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>图 2-4 对比了常数阶、线性阶和平方阶三种时间复杂度。</p>
|
||||
<p>图 2-10 对比了常数阶、线性阶和平方阶三种时间复杂度。</p>
|
||||
<p><img alt="常数阶、线性阶和平方阶的时间复杂度" src="../time_complexity.assets/time_complexity_constant_linear_quadratic.png" /></p>
|
||||
<p align="center"> 图 2-4 常数阶、线性阶和平方阶的时间复杂度 </p>
|
||||
<p align="center"> 图 2-10 常数阶、线性阶和平方阶的时间复杂度 </p>
|
||||
|
||||
<p>以冒泡排序为例,外层循环执行 <span class="arithmatex">\(n - 1\)</span> 次,内层循环执行 <span class="arithmatex">\(n-1, n-2, \dots, 2, 1\)</span> 次,平均为 <span class="arithmatex">\(n / 2\)</span> 次,因此时间复杂度为 <span class="arithmatex">\(O((n - 1) n / 2) = O(n^2)\)</span> 。</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="9:12"><input checked="checked" id="__tabbed_9_1" name="__tabbed_9" type="radio" /><input id="__tabbed_9_2" name="__tabbed_9" type="radio" /><input id="__tabbed_9_3" name="__tabbed_9" type="radio" /><input id="__tabbed_9_4" name="__tabbed_9" type="radio" /><input id="__tabbed_9_5" name="__tabbed_9" type="radio" /><input id="__tabbed_9_6" name="__tabbed_9" type="radio" /><input id="__tabbed_9_7" name="__tabbed_9" type="radio" /><input id="__tabbed_9_8" name="__tabbed_9" type="radio" /><input id="__tabbed_9_9" name="__tabbed_9" type="radio" /><input id="__tabbed_9_10" name="__tabbed_9" type="radio" /><input id="__tabbed_9_11" name="__tabbed_9" type="radio" /><input id="__tabbed_9_12" name="__tabbed_9" type="radio" /><div class="tabbed-labels"><label for="__tabbed_9_1">Java</label><label for="__tabbed_9_2">C++</label><label for="__tabbed_9_3">Python</label><label for="__tabbed_9_4">Go</label><label for="__tabbed_9_5">JS</label><label for="__tabbed_9_6">TS</label><label for="__tabbed_9_7">C</label><label for="__tabbed_9_8">C#</label><label for="__tabbed_9_9">Swift</label><label for="__tabbed_9_10">Zig</label><label for="__tabbed_9_11">Dart</label><label for="__tabbed_9_12">Rust</label></div>
|
||||
@@ -5279,7 +5301,7 @@ O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!
|
||||
</div>
|
||||
<h3 id="4-o2n">4. 指数阶 <span class="arithmatex">\(O(2^n)\)</span><a class="headerlink" href="#4-o2n" title="Permanent link">¶</a></h3>
|
||||
<p>生物学的“细胞分裂”是指数阶增长的典型例子:初始状态为 <span class="arithmatex">\(1\)</span> 个细胞,分裂一轮后变为 <span class="arithmatex">\(2\)</span> 个,分裂两轮后变为 <span class="arithmatex">\(4\)</span> 个,以此类推,分裂 <span class="arithmatex">\(n\)</span> 轮后有 <span class="arithmatex">\(2^n\)</span> 个细胞。</p>
|
||||
<p>图 2-5 和以下代码模拟了细胞分裂的过程,时间复杂度为 <span class="arithmatex">\(O(2^n)\)</span> 。</p>
|
||||
<p>图 2-11 和以下代码模拟了细胞分裂的过程,时间复杂度为 <span class="arithmatex">\(O(2^n)\)</span> 。</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="10:12"><input checked="checked" id="__tabbed_10_1" name="__tabbed_10" type="radio" /><input id="__tabbed_10_2" name="__tabbed_10" type="radio" /><input id="__tabbed_10_3" name="__tabbed_10" type="radio" /><input id="__tabbed_10_4" name="__tabbed_10" type="radio" /><input id="__tabbed_10_5" name="__tabbed_10" type="radio" /><input id="__tabbed_10_6" name="__tabbed_10" type="radio" /><input id="__tabbed_10_7" name="__tabbed_10" type="radio" /><input id="__tabbed_10_8" name="__tabbed_10" type="radio" /><input id="__tabbed_10_9" name="__tabbed_10" type="radio" /><input id="__tabbed_10_10" name="__tabbed_10" type="radio" /><input id="__tabbed_10_11" name="__tabbed_10" type="radio" /><input id="__tabbed_10_12" name="__tabbed_10" type="radio" /><div class="tabbed-labels"><label for="__tabbed_10_1">Java</label><label for="__tabbed_10_2">C++</label><label for="__tabbed_10_3">Python</label><label for="__tabbed_10_4">Go</label><label for="__tabbed_10_5">JS</label><label for="__tabbed_10_6">TS</label><label for="__tabbed_10_7">C</label><label for="__tabbed_10_8">C#</label><label for="__tabbed_10_9">Swift</label><label for="__tabbed_10_10">Zig</label><label for="__tabbed_10_11">Dart</label><label for="__tabbed_10_12">Rust</label></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
@@ -5483,7 +5505,7 @@ O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!
|
||||
</div>
|
||||
</div>
|
||||
<p><img alt="指数阶的时间复杂度" src="../time_complexity.assets/time_complexity_exponential.png" /></p>
|
||||
<p align="center"> 图 2-5 指数阶的时间复杂度 </p>
|
||||
<p align="center"> 图 2-11 指数阶的时间复杂度 </p>
|
||||
|
||||
<p>在实际算法中,指数阶常出现于递归函数中。例如在以下代码中,其递归地一分为二,经过 <span class="arithmatex">\(n\)</span> 次分裂后停止:</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="11:12"><input checked="checked" id="__tabbed_11_1" name="__tabbed_11" type="radio" /><input id="__tabbed_11_2" name="__tabbed_11" type="radio" /><input id="__tabbed_11_3" name="__tabbed_11" type="radio" /><input id="__tabbed_11_4" name="__tabbed_11" type="radio" /><input id="__tabbed_11_5" name="__tabbed_11" type="radio" /><input id="__tabbed_11_6" name="__tabbed_11" type="radio" /><input id="__tabbed_11_7" name="__tabbed_11" type="radio" /><input id="__tabbed_11_8" name="__tabbed_11" type="radio" /><input id="__tabbed_11_9" name="__tabbed_11" type="radio" /><input id="__tabbed_11_10" name="__tabbed_11" type="radio" /><input id="__tabbed_11_11" name="__tabbed_11" type="radio" /><input id="__tabbed_11_12" name="__tabbed_11" type="radio" /><div class="tabbed-labels"><label for="__tabbed_11_1">Java</label><label for="__tabbed_11_2">C++</label><label for="__tabbed_11_3">Python</label><label for="__tabbed_11_4">Go</label><label for="__tabbed_11_5">JS</label><label for="__tabbed_11_6">TS</label><label for="__tabbed_11_7">C</label><label for="__tabbed_11_8">C#</label><label for="__tabbed_11_9">Swift</label><label for="__tabbed_11_10">Zig</label><label for="__tabbed_11_11">Dart</label><label for="__tabbed_11_12">Rust</label></div>
|
||||
@@ -5598,7 +5620,7 @@ O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!
|
||||
<p>指数阶增长非常迅速,在穷举法(暴力搜索、回溯等)中比较常见。对于数据规模较大的问题,指数阶是不可接受的,通常需要使用动态规划或贪心等算法来解决。</p>
|
||||
<h3 id="5-olog-n">5. 对数阶 <span class="arithmatex">\(O(\log n)\)</span><a class="headerlink" href="#5-olog-n" title="Permanent link">¶</a></h3>
|
||||
<p>与指数阶相反,对数阶反映了“每轮缩减到一半”的情况。设输入数据大小为 <span class="arithmatex">\(n\)</span> ,由于每轮缩减到一半,因此循环次数是 <span class="arithmatex">\(\log_2 n\)</span> ,即 <span class="arithmatex">\(2^n\)</span> 的反函数。</p>
|
||||
<p>图 2-6 和以下代码模拟了“每轮缩减到一半”的过程,时间复杂度为 <span class="arithmatex">\(O(\log_2 n)\)</span> ,简记为 <span class="arithmatex">\(O(\log n)\)</span> 。</p>
|
||||
<p>图 2-12 和以下代码模拟了“每轮缩减到一半”的过程,时间复杂度为 <span class="arithmatex">\(O(\log_2 n)\)</span> ,简记为 <span class="arithmatex">\(O(\log n)\)</span> 。</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="12:12"><input checked="checked" id="__tabbed_12_1" name="__tabbed_12" type="radio" /><input id="__tabbed_12_2" name="__tabbed_12" type="radio" /><input id="__tabbed_12_3" name="__tabbed_12" type="radio" /><input id="__tabbed_12_4" name="__tabbed_12" type="radio" /><input id="__tabbed_12_5" name="__tabbed_12" type="radio" /><input id="__tabbed_12_6" name="__tabbed_12" type="radio" /><input id="__tabbed_12_7" name="__tabbed_12" type="radio" /><input id="__tabbed_12_8" name="__tabbed_12" type="radio" /><input id="__tabbed_12_9" name="__tabbed_12" type="radio" /><input id="__tabbed_12_10" name="__tabbed_12" type="radio" /><input id="__tabbed_12_11" name="__tabbed_12" type="radio" /><input id="__tabbed_12_12" name="__tabbed_12" type="radio" /><div class="tabbed-labels"><label for="__tabbed_12_1">Java</label><label for="__tabbed_12_2">C++</label><label for="__tabbed_12_3">Python</label><label for="__tabbed_12_4">Go</label><label for="__tabbed_12_5">JS</label><label for="__tabbed_12_6">TS</label><label for="__tabbed_12_7">C</label><label for="__tabbed_12_8">C#</label><label for="__tabbed_12_9">Swift</label><label for="__tabbed_12_10">Zig</label><label for="__tabbed_12_11">Dart</label><label for="__tabbed_12_12">Rust</label></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
@@ -5749,7 +5771,7 @@ O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!
|
||||
</div>
|
||||
</div>
|
||||
<p><img alt="对数阶的时间复杂度" src="../time_complexity.assets/time_complexity_logarithmic.png" /></p>
|
||||
<p align="center"> 图 2-6 对数阶的时间复杂度 </p>
|
||||
<p align="center"> 图 2-12 对数阶的时间复杂度 </p>
|
||||
|
||||
<p>与指数阶类似,对数阶也常出现于递归函数中。以下代码形成了一个高度为 <span class="arithmatex">\(\log_2 n\)</span> 的递归树:</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="13:12"><input checked="checked" id="__tabbed_13_1" name="__tabbed_13" type="radio" /><input id="__tabbed_13_2" name="__tabbed_13" type="radio" /><input id="__tabbed_13_3" name="__tabbed_13" type="radio" /><input id="__tabbed_13_4" name="__tabbed_13" type="radio" /><input id="__tabbed_13_5" name="__tabbed_13" type="radio" /><input id="__tabbed_13_6" name="__tabbed_13" type="radio" /><input id="__tabbed_13_7" name="__tabbed_13" type="radio" /><input id="__tabbed_13_8" name="__tabbed_13" type="radio" /><input id="__tabbed_13_9" name="__tabbed_13" type="radio" /><input id="__tabbed_13_10" name="__tabbed_13" type="radio" /><input id="__tabbed_13_11" name="__tabbed_13" type="radio" /><input id="__tabbed_13_12" name="__tabbed_13" type="radio" /><div class="tabbed-labels"><label for="__tabbed_13_1">Java</label><label for="__tabbed_13_2">C++</label><label for="__tabbed_13_3">Python</label><label for="__tabbed_13_4">Go</label><label for="__tabbed_13_5">JS</label><label for="__tabbed_13_6">TS</label><label for="__tabbed_13_7">C</label><label for="__tabbed_13_8">C#</label><label for="__tabbed_13_9">Swift</label><label for="__tabbed_13_10">Zig</label><label for="__tabbed_13_11">Dart</label><label for="__tabbed_13_12">Rust</label></div>
|
||||
@@ -6034,9 +6056,9 @@ O(\log_m n) = O(\log_k n / \log_k m) = O(\log_k n)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>图 2-7 展示了线性对数阶的生成方式。二叉树的每一层的操作总数都为 <span class="arithmatex">\(n\)</span> ,树共有 <span class="arithmatex">\(\log_2 n + 1\)</span> 层,因此时间复杂度为 <span class="arithmatex">\(O(n \log n)\)</span> 。</p>
|
||||
<p>图 2-13 展示了线性对数阶的生成方式。二叉树的每一层的操作总数都为 <span class="arithmatex">\(n\)</span> ,树共有 <span class="arithmatex">\(\log_2 n + 1\)</span> 层,因此时间复杂度为 <span class="arithmatex">\(O(n \log n)\)</span> 。</p>
|
||||
<p><img alt="线性对数阶的时间复杂度" src="../time_complexity.assets/time_complexity_logarithmic_linear.png" /></p>
|
||||
<p align="center"> 图 2-7 线性对数阶的时间复杂度 </p>
|
||||
<p align="center"> 图 2-13 线性对数阶的时间复杂度 </p>
|
||||
|
||||
<p>主流排序算法的时间复杂度通常为 <span class="arithmatex">\(O(n \log n)\)</span> ,例如快速排序、归并排序、堆排序等。</p>
|
||||
<h3 id="7-on">7. 阶乘阶 <span class="arithmatex">\(O(n!)\)</span><a class="headerlink" href="#7-on" title="Permanent link">¶</a></h3>
|
||||
@@ -6044,7 +6066,7 @@ O(\log_m n) = O(\log_k n / \log_k m) = O(\log_k n)
|
||||
<div class="arithmatex">\[
|
||||
n! = n \times (n - 1) \times (n - 2) \times \dots \times 2 \times 1
|
||||
\]</div>
|
||||
<p>阶乘通常使用递归实现。如图 2-8 和以下代码所示,第一层分裂出 <span class="arithmatex">\(n\)</span> 个,第二层分裂出 <span class="arithmatex">\(n - 1\)</span> 个,以此类推,直至第 <span class="arithmatex">\(n\)</span> 层时停止分裂:</p>
|
||||
<p>阶乘通常使用递归实现。如图 2-14 和以下代码所示,第一层分裂出 <span class="arithmatex">\(n\)</span> 个,第二层分裂出 <span class="arithmatex">\(n - 1\)</span> 个,以此类推,直至第 <span class="arithmatex">\(n\)</span> 层时停止分裂:</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="15:12"><input checked="checked" id="__tabbed_15_1" name="__tabbed_15" type="radio" /><input id="__tabbed_15_2" name="__tabbed_15" type="radio" /><input id="__tabbed_15_3" name="__tabbed_15" type="radio" /><input id="__tabbed_15_4" name="__tabbed_15" type="radio" /><input id="__tabbed_15_5" name="__tabbed_15" type="radio" /><input id="__tabbed_15_6" name="__tabbed_15" type="radio" /><input id="__tabbed_15_7" name="__tabbed_15" type="radio" /><input id="__tabbed_15_8" name="__tabbed_15" type="radio" /><input id="__tabbed_15_9" name="__tabbed_15" type="radio" /><input id="__tabbed_15_10" name="__tabbed_15" type="radio" /><input id="__tabbed_15_11" name="__tabbed_15" type="radio" /><input id="__tabbed_15_12" name="__tabbed_15" type="radio" /><div class="tabbed-labels"><label for="__tabbed_15_1">Java</label><label for="__tabbed_15_2">C++</label><label for="__tabbed_15_3">Python</label><label for="__tabbed_15_4">Go</label><label for="__tabbed_15_5">JS</label><label for="__tabbed_15_6">TS</label><label for="__tabbed_15_7">C</label><label for="__tabbed_15_8">C#</label><label for="__tabbed_15_9">Swift</label><label for="__tabbed_15_10">Zig</label><label for="__tabbed_15_11">Dart</label><label for="__tabbed_15_12">Rust</label></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
@@ -6214,10 +6236,10 @@ n! = n \times (n - 1) \times (n - 2) \times \dots \times 2 \times 1
|
||||
</div>
|
||||
</div>
|
||||
<p><img alt="阶乘阶的时间复杂度" src="../time_complexity.assets/time_complexity_factorial.png" /></p>
|
||||
<p align="center"> 图 2-8 阶乘阶的时间复杂度 </p>
|
||||
<p align="center"> 图 2-14 阶乘阶的时间复杂度 </p>
|
||||
|
||||
<p>请注意,因为当 <span class="arithmatex">\(n \geq 4\)</span> 时恒有 <span class="arithmatex">\(n! > 2^n\)</span> ,所以阶乘阶比指数阶增长得更快,在 <span class="arithmatex">\(n\)</span> 较大时也是不可接受的。</p>
|
||||
<h2 id="225">2.2.5 最差、最佳、平均时间复杂度<a class="headerlink" href="#225" title="Permanent link">¶</a></h2>
|
||||
<h2 id="235">2.3.5 最差、最佳、平均时间复杂度<a class="headerlink" href="#235" title="Permanent link">¶</a></h2>
|
||||
<p><strong>算法的时间效率往往不是固定的,而是与输入数据的分布有关</strong>。假设输入一个长度为 <span class="arithmatex">\(n\)</span> 的数组 <code>nums</code> ,其中 <code>nums</code> 由从 <span class="arithmatex">\(1\)</span> 至 <span class="arithmatex">\(n\)</span> 的数字组成,每个数字只出现一次,但元素顺序是随机打乱的,任务目标是返回元素 <span class="arithmatex">\(1\)</span> 的索引。我们可以得出以下结论。</p>
|
||||
<ul>
|
||||
<li>当 <code>nums = [?, ?, ..., 1]</code> ,即当末尾元素是 <span class="arithmatex">\(1\)</span> 时,需要完整遍历数组,<strong>达到最差时间复杂度 <span class="arithmatex">\(O(n)\)</span></strong> 。</li>
|
||||
@@ -6640,7 +6662,7 @@ n! = n \times (n - 1) \times (n - 2) \times \dots \times 2 \times 1
|
||||
<nav class="md-footer__inner md-grid" aria-label="页脚" >
|
||||
|
||||
|
||||
<a href="../performance_evaluation/" class="md-footer__link md-footer__link--prev" aria-label="上一页: 2.1 &nbsp; 算法效率评估" rel="prev">
|
||||
<a href="../iteration_and_recursion/" class="md-footer__link md-footer__link--prev" aria-label="上一页: 2.2 &nbsp; 迭代与递归" rel="prev">
|
||||
<div class="md-footer__button md-icon">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12Z"/></svg>
|
||||
@@ -6650,20 +6672,20 @@ n! = n \times (n - 1) \times (n - 2) \times \dots \times 2 \times 1
|
||||
上一页
|
||||
</span>
|
||||
<div class="md-ellipsis">
|
||||
2.1 算法效率评估
|
||||
2.2 迭代与递归
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
<a href="../space_complexity/" class="md-footer__link md-footer__link--next" aria-label="下一页: 2.3 &nbsp; 空间复杂度" rel="next">
|
||||
<a href="../space_complexity/" class="md-footer__link md-footer__link--next" aria-label="下一页: 2.4 &nbsp; 空间复杂度" rel="next">
|
||||
<div class="md-footer__title">
|
||||
<span class="md-footer__direction">
|
||||
下一页
|
||||
</span>
|
||||
<div class="md-ellipsis">
|
||||
2.3 空间复杂度
|
||||
2.4 空间复杂度
|
||||
</div>
|
||||
</div>
|
||||
<div class="md-footer__button md-icon">
|
||||
|
||||
Reference in New Issue
Block a user