This commit is contained in:
krahets
2023-07-24 03:03:58 +08:00
parent a86a371780
commit 3e2ab6a857
19 changed files with 379 additions and 456 deletions

View File

@@ -3396,7 +3396,7 @@
<p>除哈希表外,我们还可以使用数组或链表实现查询功能。若将学生数据看作数组(链表)元素,则有:</p>
<ul>
<li><strong>添加元素</strong>:仅需将元素添加至数组(链表)的尾部即可,使用 <span class="arithmatex">\(O(1)\)</span> 时间;</li>
<li><strong>查询元素</strong>:由于数组(链表)是乱序的,因此需要遍历数组(链表)中的所有元素,使用 <span class="arithmatex">\(O(n)\)</span> 时间;</li>
<li><strong>查询元素</strong>:由于数组(链表)是乱序的,因此需要遍历中的所有元素,使用 <span class="arithmatex">\(O(n)\)</span> 时间;</li>
<li><strong>删除元素</strong>:需要先查询到元素,再从数组中删除,使用 <span class="arithmatex">\(O(n)\)</span> 时间;</li>
</ul>
<div class="center-table">
@@ -3431,7 +3431,7 @@
</tbody>
</table>
</div>
<p>观察发现,<strong>在哈希表中进行增删查改的时间复杂度都是 <span class="arithmatex">\(O(1)\)</span></strong> ,非常高效。因此,哈希表常用于对查找效率要求较高的场景。</p>
<p>观察发现,<strong>在哈希表中进行增删查改的时间复杂度都是 <span class="arithmatex">\(O(1)\)</span></strong> ,非常高效。</p>
<h2 id="611">6.1.1. &nbsp; 哈希表常用操作<a class="headerlink" href="#611" title="Permanent link">&para;</a></h2>
<p>哈希表的常见操作包括:初始化、查询操作、添加键值对和删除键值对等。</p>
<div class="tabbed-set tabbed-alternate" data-tabs="1:11"><input checked="checked" id="__tabbed_1_1" name="__tabbed_1" type="radio" /><input id="__tabbed_1_2" name="__tabbed_1" type="radio" /><input id="__tabbed_1_3" name="__tabbed_1" type="radio" /><input id="__tabbed_1_4" name="__tabbed_1" type="radio" /><input id="__tabbed_1_5" name="__tabbed_1" type="radio" /><input id="__tabbed_1_6" name="__tabbed_1" type="radio" /><input id="__tabbed_1_7" name="__tabbed_1" type="radio" /><input id="__tabbed_1_8" name="__tabbed_1" type="radio" /><input id="__tabbed_1_9" name="__tabbed_1" type="radio" /><input id="__tabbed_1_10" name="__tabbed_1" type="radio" /><input id="__tabbed_1_11" name="__tabbed_1" type="radio" /><div class="tabbed-labels"><label for="__tabbed_1_1">Java</label><label for="__tabbed_1_2">C++</label><label for="__tabbed_1_3">Python</label><label for="__tabbed_1_4">Go</label><label for="__tabbed_1_5">JavaScript</label><label for="__tabbed_1_6">TypeScript</label><label for="__tabbed_1_7">C</label><label for="__tabbed_1_8">C#</label><label for="__tabbed_1_9">Swift</label><label for="__tabbed_1_10">Zig</label><label for="__tabbed_1_11">Dart</label></div>
@@ -3800,7 +3800,11 @@
<h2 id="612">6.1.2. &nbsp; 哈希表简单实现<a class="headerlink" href="#612" title="Permanent link">&para;</a></h2>
<p>我们先考虑最简单的情况,<strong>仅用一个数组来实现哈希表</strong>。在哈希表中,我们将数组中的每个空位称为「桶 Bucket」每个桶可存储一个键值对。因此查询操作就是找到 <code>key</code> 对应的桶,并在桶中获取 <code>value</code></p>
<p>那么,如何基于 <code>key</code> 来定位对应的桶呢?这是通过「哈希函数 Hash Function」实现的。哈希函数的作用是将一个较大的输入空间映射到一个较小的输出空间。在哈希表中输入空间是所有 <code>key</code> ,输出空间是所有桶(数组索引)。换句话说,输入一个 <code>key</code> <strong>我们可以通过哈希函数得到该 <code>key</code> 对应的键值对在数组中的存储位置</strong></p>
<p>输入一个 <code>key</code> ,哈希函数的计算过程分为两步:首先,通过哈希算法 <code>hash()</code> 计算得到哈希值;接下来,将哈希值对桶数量(数组长度)<code>capacity</code> 取模,从而获取该 <code>key</code> 对应的数组索引 <code>index</code></p>
<p>输入一个 <code>key</code> ,哈希函数的计算过程分为两步:</p>
<ol>
<li>通过某种哈希算法 <code>hash()</code> 计算得到哈希值;</li>
<li>将哈希值对桶数量(数组长度)<code>capacity</code> 取模,从而获取该 <code>key</code> 对应的数组索引 <code>index</code> </li>
</ol>
<div class="highlight"><pre><span></span><code><a id="__codelineno-22-1" name="__codelineno-22-1" href="#__codelineno-22-1"></a><span class="nv">index</span><span class="w"> </span><span class="o">=</span><span class="w"> </span>hash<span class="o">(</span>key<span class="o">)</span><span class="w"> </span>%<span class="w"> </span>capacity
</code></pre></div>
<p>随后,我们就可以利用 <code>index</code> 在哈希表中访问对应的桶,从而获取 <code>value</code></p>
@@ -3808,7 +3812,7 @@
<p><img alt="哈希函数工作原理" src="../hash_map.assets/hash_function.png" /></p>
<p align="center"> Fig. 哈希函数工作原理 </p>
<p>以下代码给出了一个简单哈希表实现。其中,我们将 <code>key</code><code>value</code> 封装成一个类 <code>Pair</code> ,以表示键值对。</p>
<p>以下代码实现了一个简单哈希表。其中,我们将 <code>key</code><code>value</code> 封装成一个类 <code>Pair</code> ,以表示键值对。</p>
<div class="tabbed-set tabbed-alternate" data-tabs="3:11"><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" /><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><label for="__tabbed_3_11">Dart</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
@@ -4739,7 +4743,7 @@
</div>
</div>
<h2 id="613">6.1.3. &nbsp; 哈希冲突与扩容<a class="headerlink" href="#613" title="Permanent link">&para;</a></h2>
<p>本质上看,哈希函数的作用是将输入空间(<code>key</code> 范围)映射到输出空间(数组索引范围),而输入空间往往远大于输出空间。因此,<strong>理论上一定存在“多个输入对应相同输出”的情况</strong></p>
<p>本质上看,哈希函数的作用是将所有 <code>key</code> 构成的输入空间映射到数组所有索引构成的输出空间,而输入空间往往远大于输出空间。因此,<strong>理论上一定存在“多个输入对应相同输出”的情况</strong></p>
<p>对于上述示例中的哈希函数,当输入的 <code>key</code> 后两位相同时,哈希函数的输出结果也相同。例如,查询学号为 12836 和 20336 的两个学生时,我们得到:</p>
<div class="highlight"><pre><span></span><code><a id="__codelineno-34-1" name="__codelineno-34-1" href="#__codelineno-34-1"></a><span class="m">12836</span><span class="w"> </span>%<span class="w"> </span><span class="nv">100</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">36</span>
<a id="__codelineno-34-2" name="__codelineno-34-2" href="#__codelineno-34-2"></a><span class="m">20336</span><span class="w"> </span>%<span class="w"> </span><span class="nv">100</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">36</span>
@@ -4752,8 +4756,8 @@
<p><img alt="哈希表扩容" src="../hash_map.assets/hash_table_reshash.png" /></p>
<p align="center"> Fig. 哈希表扩容 </p>
<p>类似于数组扩容,哈希表扩容需将所有键值对从原哈希表迁移至新哈希表,非常耗时。并且由于哈希表容量 <code>capacity</code> 改变,我们需要重新计算所有键值对的存储位置,进一步提高了扩容过程的计算开销。此,编程语言通常会预留足够大的哈希表容量,防止频繁扩容。</p>
<p>在哈希表中,「负载因子 Load Factor」是一个重要概念其定义为哈希表的元素数量除以桶数量为了衡量哈希冲突的严重程度,<strong>也常被作为哈希表扩容的触发条件</strong>。例如在 Java 中,当负载因子超过 <span class="arithmatex">\(0.75\)</span> 时,系统会将哈希表容量扩展为原先的 <span class="arithmatex">\(2\)</span> 倍。</p>
<p>类似于数组扩容,哈希表扩容需将所有键值对从原哈希表迁移至新哈希表,非常耗时。并且由于哈希表容量 <code>capacity</code> 改变,我们需要通过哈希函数来重新计算所有键值对的存储位置,进一步提高了扩容过程的计算开销。此,编程语言通常会预留足够大的哈希表容量,防止频繁扩容。</p>
<p>「负载因子 Load Factor」是哈希表的一个重要概念,其定义为哈希表的元素数量除以桶数量,用于衡量哈希冲突的严重程度,<strong>也常被作为哈希表扩容的触发条件</strong>。例如在 Java 中,当负载因子超过 <span class="arithmatex">\(0.75\)</span> 时,系统会将哈希表容量扩展为原先的 <span class="arithmatex">\(2\)</span> 倍。</p>