This commit is contained in:
krahets
2023-08-17 05:12:16 +08:00
parent 2014338a92
commit 5884de5246
70 changed files with 1890 additions and 1219 deletions

View File

@ -3429,7 +3429,7 @@
<p>散列表,又称「哈希表 Hash Table」其通过建立键 <code>key</code> 与值 <code>value</code> 之间的映射,实现高效的元素查询。具体而言,我们向哈希表输入一个键 <code>key</code> ,则可以在 <span class="arithmatex">\(O(1)\)</span> 时间内获取对应的值 <code>value</code></p>
<p>以一个包含 <span class="arithmatex">\(n\)</span> 个学生的数据库为例,每个学生都有“姓名”和“学号”两项数据。假如我们希望实现“输入一个学号,返回对应的姓名”的查询功能,则可以采用哈希表来实现。</p>
<p><img alt="哈希表的抽象表示" src="../hash_map.assets/hash_table_lookup.png" /></p>
<p align="center"> Fig. 哈希表的抽象表示 </p>
<p align="center"> 图:哈希表的抽象表示 </p>
<p>除哈希表外,我们还可以使用数组或链表实现查询功能。若将学生数据看作数组(链表)元素,则有:</p>
<ul>
@ -3853,7 +3853,7 @@
<p>随后,我们就可以利用 <code>index</code> 在哈希表中访问对应的桶,从而获取 <code>value</code></p>
<p>设数组长度 <code>capacity = 100</code> 、哈希算法 <code>hash(key) = key</code> ,易得哈希函数为 <code>key % 100</code> 。下图以 <code>key</code> 学号和 <code>value</code> 姓名为例,展示了哈希函数的工作原理。</p>
<p><img alt="哈希函数工作原理" src="../hash_map.assets/hash_function.png" /></p>
<p align="center"> Fig. 哈希函数工作原理 </p>
<p align="center"> 图:哈希函数工作原理 </p>
<p>以下代码实现了一个简单哈希表。其中,我们将 <code>key</code><code>value</code> 封装成一个类 <code>Pair</code> ,以表示键值对。</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>
@ -4862,11 +4862,11 @@
</code></pre></div>
<p>如下图所示,两个学号指向了同一个姓名,这显然是不对的。我们将这种多个输入对应同一输出的情况称为「哈希冲突 Hash Collision」。</p>
<p><img alt="哈希冲突示例" src="../hash_map.assets/hash_collision.png" /></p>
<p align="center"> Fig. 哈希冲突示例 </p>
<p align="center"> 图:哈希冲突示例 </p>
<p>容易想到,哈希表容量 <span class="arithmatex">\(n\)</span> 越大,多个 <code>key</code> 被分配到同一个桶中的概率就越低,冲突就越少。因此,<strong>我们可以通过扩容哈希表来减少哈希冲突</strong>。如下图所示,扩容前键值对 <code>(136, A)</code><code>(236, D)</code> 发生冲突,扩容后冲突消失。</p>
<p><img alt="哈希表扩容" src="../hash_map.assets/hash_table_reshash.png" /></p>
<p align="center"> Fig. 哈希表扩容 </p>
<p align="center"> 图:哈希表扩容 </p>
<p>类似于数组扩容,哈希表扩容需将所有键值对从原哈希表迁移至新哈希表,非常耗时。并且由于哈希表容量 <code>capacity</code> 改变,我们需要通过哈希函数来重新计算所有键值对的存储位置,这进一步提高了扩容过程的计算开销。为此,编程语言通常会预留足够大的哈希表容量,防止频繁扩容。</p>
<p>「负载因子 Load Factor」是哈希表的一个重要概念其定义为哈希表的元素数量除以桶数量用于衡量哈希冲突的严重程度<strong>也常被作为哈希表扩容的触发条件</strong>。例如在 Java 中,当负载因子超过 <span class="arithmatex">\(0.75\)</span> 时,系统会将哈希表容量扩展为原先的 <span class="arithmatex">\(2\)</span> 倍。</p>