This commit is contained in:
krahets
2023-08-27 23:41:10 +08:00
parent 8c9cf3f087
commit 016f13d882
66 changed files with 262 additions and 270 deletions

View File

@ -3451,10 +3451,10 @@
<p align="center"> 图 10-1 &nbsp; 二分查找示例数据 </p>
<p>如图 10-2 所示,我们先初始化指针 <span class="arithmatex">\(i = 0\)</span><span class="arithmatex">\(j = n - 1\)</span> ,分别指向数组首元素和尾元素,代表搜索区间 <span class="arithmatex">\([0, n - 1]\)</span> 。请注意,中括号表示闭区间,其包含边界值本身。</p>
<p>接下来,循环执行以下两个步骤:</p>
<p>接下来,循环执行以下两步。</p>
<ol>
<li>计算中点索引 <span class="arithmatex">\(m = \lfloor {(i + j) / 2} \rfloor\)</span> ,其中 <span class="arithmatex">\(\lfloor \space \rfloor\)</span> 表示向下取整操作。</li>
<li>判断 <code>nums[m]</code><code>target</code> 的大小关系,分为三种情况<ol>
<li>判断 <code>nums[m]</code><code>target</code> 的大小关系,分为以下三种情况<ol>
<li><code>nums[m] &lt; target</code> 时,说明 <code>target</code> 在区间 <span class="arithmatex">\([m + 1, j]\)</span> 中,因此执行 <span class="arithmatex">\(i = m + 1\)</span></li>
<li><code>nums[m] &gt; target</code> 时,说明 <code>target</code> 在区间 <span class="arithmatex">\([i, m - 1]\)</span> 中,因此执行 <span class="arithmatex">\(j = m - 1\)</span></li>
<li><code>nums[m] = target</code> 时,说明找到 <code>target</code> ,因此返回索引 <span class="arithmatex">\(m\)</span></li>
@ -3752,7 +3752,7 @@
</div>
</div>
<p>时间复杂度为 <span class="arithmatex">\(O(\log n)\)</span> 。每轮缩小一半区间,因此二分循环次数为 <span class="arithmatex">\(\log_2 n\)</span></p>
<p>空间复杂度为 <span class="arithmatex">\(O(1)\)</span> 。指针 <code>i</code> , <code>j</code> 使用常数大小空间。</p>
<p>空间复杂度为 <span class="arithmatex">\(O(1)\)</span> 。指针 <span class="arithmatex">\(i\)</span> <span class="arithmatex">\(j\)</span> 使用常数大小空间。</p>
<h2 id="1011">10.1.1 &nbsp; 区间表示方法<a class="headerlink" href="#1011" title="Permanent link">&para;</a></h2>
<p>除了上述的双闭区间外,常见的区间表示还有“左闭右开”区间,定义为 <span class="arithmatex">\([0, n)\)</span> ,即左边界包含自身,右边界不包含自身。在该表示下,区间 <span class="arithmatex">\([i, j]\)</span><span class="arithmatex">\(i = j\)</span> 时为空。</p>
<p>我们可以基于该表示实现具有相同功能的二分查找算法。</p>
@ -4023,12 +4023,12 @@
<p align="center"> 图 10-3 &nbsp; 两种区间定义 </p>
<h2 id="1012">10.1.2 &nbsp; 优点与局限性<a class="headerlink" href="#1012" title="Permanent link">&para;</a></h2>
<p>二分查找在时间和空间方面都有较好的性能</p>
<p>二分查找在时间和空间方面都有较好的性能</p>
<ul>
<li>二分查找的时间效率高。在大数据量下,对数阶的时间复杂度具有显著优势。例如,当数据大小 <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>二分查找无须额外空间。相较于需要借助额外空间的搜索算法(例如哈希查找),二分查找更加节省空间。</li>
</ul>
<p>然而,二分查找并非适用于所有情况,原因如下:</p>
<p>然而,二分查找并非适用于所有情况,主要有以下原因。</p>
<ul>
<li>二分查找仅适用于有序数据。若输入数据无序,为了使用二分查找而专门进行排序,得不偿失。因为排序算法的时间复杂度通常为 <span class="arithmatex">\(O(n \log n)\)</span> ,比线性查找和二分查找都更高。对于频繁插入元素的场景,为保持数组有序性,需要将元素插入到特定位置,时间复杂度为 <span class="arithmatex">\(O(n)\)</span> ,也是非常昂贵的。</li>
<li>二分查找仅适用于数组。二分查找需要跳跃式(非连续地)访问元素,而在链表中执行跳跃式访问的效率较低,因此不适合应用在链表或基于链表实现的数据结构。</li>

View File

@ -3496,11 +3496,11 @@
<p>给定一个长度为 <span class="arithmatex">\(n\)</span> 的有序数组 <code>nums</code> ,数组可能包含重复元素。请返回数组中最左一个元素 <code>target</code> 的索引。若数组中不包含该元素,则返回 <span class="arithmatex">\(-1\)</span></p>
</div>
<p>回忆二分查找插入点的方法,搜索完成后 <span class="arithmatex">\(i\)</span> 指向最左一个 <code>target</code> <strong>因此查找插入点本质上是在查找最左一个 <code>target</code> 的索引</strong></p>
<p>考虑通过查找插入点的函数实现查找左边界。请注意,数组中可能不包含 <code>target</code> 此时有两种可能:</p>
<ol>
<li>插入点的索引 <span class="arithmatex">\(i\)</span> 越界</li>
<li>元素 <code>nums[i]</code><code>target</code> 不相等</li>
</ol>
<p>考虑通过查找插入点的函数实现查找左边界。请注意,数组中可能不包含 <code>target</code> 这种情况可能导致以下两种结果。</p>
<ul>
<li>插入点的索引 <span class="arithmatex">\(i\)</span> 越界</li>
<li>元素 <code>nums[i]</code><code>target</code> 不相等</li>
</ul>
<p>当遇到以上两种情况时,直接返回 <span class="arithmatex">\(-1\)</span> 即可。</p>
<div class="tabbed-set tabbed-alternate" data-tabs="1:12"><input checked="checked" id="__tabbed_1_1" name="__tabbed_1" type="radio" /><input id="__tabbed_1_2" name="__tabbed_1" type="radio" /><input id="__tabbed_1_3" name="__tabbed_1" type="radio" /><input id="__tabbed_1_4" name="__tabbed_1" type="radio" /><input id="__tabbed_1_5" name="__tabbed_1" type="radio" /><input id="__tabbed_1_6" name="__tabbed_1" type="radio" /><input id="__tabbed_1_7" name="__tabbed_1" type="radio" /><input id="__tabbed_1_8" name="__tabbed_1" type="radio" /><input id="__tabbed_1_9" name="__tabbed_1" type="radio" /><input id="__tabbed_1_10" name="__tabbed_1" type="radio" /><input id="__tabbed_1_11" name="__tabbed_1" type="radio" /><input id="__tabbed_1_12" name="__tabbed_1" type="radio" /><div class="tabbed-labels"><label for="__tabbed_1_1">Java</label><label for="__tabbed_1_2">C++</label><label for="__tabbed_1_3">Python</label><label for="__tabbed_1_4">Go</label><label for="__tabbed_1_5">JS</label><label for="__tabbed_1_6">TS</label><label for="__tabbed_1_7">C</label><label for="__tabbed_1_8">C#</label><label for="__tabbed_1_9">Swift</label><label for="__tabbed_1_10">Zig</label><label for="__tabbed_1_11">Dart</label><label for="__tabbed_1_12">Rust</label></div>
<div class="tabbed-content">
@ -3767,8 +3767,8 @@
</div>
</div>
<h3 id="2">2. &nbsp; 转化为查找元素<a class="headerlink" href="#2" title="Permanent link">&para;</a></h3>
<p>我们知道,当数组不包含 <code>target</code> 时,最 <span class="arithmatex">\(i\)</span> , <span class="arithmatex">\(j\)</span> 会分别指向首个大于、小于 <code>target</code> 的元素。</p>
<p>根据上述结论,我们可以构造一个数组中不存在的元素,用于查找左右边界,如图 10-8 所示</p>
<p>我们知道,当数组不包含 <code>target</code> 时,最 <span class="arithmatex">\(i\)</span> <span class="arithmatex">\(j\)</span> 会分别指向首个大于、小于 <code>target</code> 的元素。</p>
<p>因此,如图 10-8 所示,我们可以构造一个数组中不存在的元素,用于查找左右边界。</p>
<ul>
<li>查找最左一个 <code>target</code> :可以转化为查找 <code>target - 0.5</code> ,并返回指针 <span class="arithmatex">\(i\)</span></li>
<li>查找最右一个 <code>target</code> :可以转化为查找 <code>target + 0.5</code> ,并返回指针 <span class="arithmatex">\(j\)</span></li>
@ -3776,7 +3776,7 @@
<p><img alt="将查找边界转化为查找元素" src="../binary_search_edge.assets/binary_search_edge_by_element.png" /></p>
<p align="center"> 图 10-8 &nbsp; 将查找边界转化为查找元素 </p>
<p>代码在此省略,值得注意的有:</p>
<p>代码在此省略,值得注意以下两点。</p>
<ul>
<li>给定数组不包含小数,这意味着我们无须关心如何处理相等的情况。</li>
<li>因为该方法引入了小数,所以需要将函数中的变量 <code>target</code> 改为浮点数类型。</li>

View File

@ -3639,7 +3639,7 @@
<p align="center"> 图 10-5 &nbsp; 线性查找重复元素的插入点 </p>
<p>此方法虽然可用,但其包含线性查找,因此时间复杂度为 <span class="arithmatex">\(O(n)\)</span> 。当数组中存在很多重复的 <code>target</code> 时,该方法效率很低。</p>
<p>现考虑拓展二分查找代码。如图 10-6 所示,整体流程保持不变,每轮先计算中点索引 <span class="arithmatex">\(m\)</span> ,再判断 <code>target</code><code>nums[m]</code> 大小关系</p>
<p>现考虑拓展二分查找代码。如图 10-6 所示,整体流程保持不变,每轮先计算中点索引 <span class="arithmatex">\(m\)</span> ,再判断 <code>target</code><code>nums[m]</code> 大小关系</p>
<ol>
<li><code>nums[m] &lt; target</code><code>nums[m] &gt; target</code> 时,说明还没有找到 <code>target</code> ,因此采用普通二分查找的缩小区间操作,<strong>从而使指针 <span class="arithmatex">\(i\)</span><span class="arithmatex">\(j\)</span><code>target</code> 靠近</strong></li>
<li><code>nums[m] == target</code> 时,说明小于 <code>target</code> 的元素在区间 <span class="arithmatex">\([i, m - 1]\)</span> 中,因此采用 <span class="arithmatex">\(j = m - 1\)</span> 来缩小区间,<strong>从而使指针 <span class="arithmatex">\(j\)</span> 向小于 <code>target</code> 的元素靠近</strong></li>
@ -3840,8 +3840,8 @@
<p class="admonition-title">Tip</p>
<p>本节的代码都是“双闭区间”写法。有兴趣的读者可以自行实现“左闭右开”写法。</p>
</div>
<p>总的来看,二分查找无非就是给指针 <span class="arithmatex">\(i\)</span> , <span class="arithmatex">\(j\)</span> 分别设定搜索目标,目标可能是一个具体的元素(例如 <code>target</code> ),也可能是一个元素范围(例如小于 <code>target</code> 的元素)。</p>
<p>在不断的循环二分中,指针 <span class="arithmatex">\(i\)</span> , <span class="arithmatex">\(j\)</span> 都逐渐逼近预先设定的目标。最终,它们或是成功找到答案,或是越过边界后停止。</p>
<p>总的来看,二分查找无非就是给指针 <span class="arithmatex">\(i\)</span> <span class="arithmatex">\(j\)</span> 分别设定搜索目标,目标可能是一个具体的元素(例如 <code>target</code> ),也可能是一个元素范围(例如小于 <code>target</code> 的元素)。</p>
<p>在不断的循环二分中,指针 <span class="arithmatex">\(i\)</span> <span class="arithmatex">\(j\)</span> 都逐渐逼近预先设定的目标。最终,它们或是成功找到答案,或是越过边界后停止。</p>

View File

@ -3457,7 +3457,7 @@
<h1 id="105">10.5 &nbsp; 重识搜索算法<a class="headerlink" href="#105" title="Permanent link">&para;</a></h1>
<p>「搜索算法 searching algorithm」用于在数据结构例如数组、链表、树或图中搜索一个或一组满足特定条件的元素。</p>
<p>根据实现思路,搜索算法总体可分为两种:</p>
<p>搜索算法可根据实现思路分为以下两类。</p>
<ul>
<li><strong>通过遍历数据结构来定位目标元素</strong>,例如数组、链表、树和图的遍历等。</li>
<li><strong>利用数据组织结构或数据包含的先验信息,实现高效元素查找</strong>,例如二分查找、哈希查找和二叉搜索树查找等。</li>