This commit is contained in:
krahets
2023-08-21 19:32:49 +08:00
parent c0f960b443
commit c359c07fe0
67 changed files with 443 additions and 442 deletions
+1 -1
View File
@@ -3441,7 +3441,7 @@
<h1 id="63">6.3 &nbsp; 哈希算法<a class="headerlink" href="#63" title="Permanent link">&para;</a></h1>
<p>在上两节中,我们了解了哈希表的工作原理和哈希冲突的处理方法。然而无论是开放寻址还是链地址法,<strong>它们只能保证哈希表可以在发生冲突时正常工作,但无法减少哈希冲突的发生</strong></p>
<p>如果哈希冲突过于频繁,哈希表的性能则会急剧劣化。对于链地址哈希表,理想情况下键值对平均分布在各个桶中,达到最佳查询效率;最差情况下所有键值对都被存储到同一个桶中,时间复杂度退化至 <span class="arithmatex">\(O(n)\)</span></p>
<p>如果哈希冲突过于频繁,哈希表的性能则会急剧劣化。如下图所示,对于链地址哈希表,理想情况下键值对平均分布在各个桶中,达到最佳查询效率;最差情况下所有键值对都被存储到同一个桶中,时间复杂度退化至 <span class="arithmatex">\(O(n)\)</span></p>
<p><img alt="哈希冲突的最佳与最差情况" src="../hash_algorithm.assets/hash_collision_best_worst_condition.png" /></p>
<p align="center"> 图:哈希冲突的最佳与最差情况 </p>
+4 -3
View File
@@ -3474,7 +3474,7 @@
</ol>
<p>哈希表的结构改良方法主要包括链式地址和开放寻址。</p>
<h2 id="621">6.2.1 &nbsp; 链式地址<a class="headerlink" href="#621" title="Permanent link">&para;</a></h2>
<p>在原始哈希表中,每个桶仅能存储一个键值对。「链式地址 separate chaining」将单个元素转换为链表,将键值对作为链表节点,将所有发生冲突的键值对都存储在同一链表中。</p>
<p>在原始哈希表中,每个桶仅能存储一个键值对。「链式地址 separate chaining」将单个元素转换为链表,将键值对作为链表节点,将所有发生冲突的键值对都存储在同一链表中。下图展示了一个链式地址哈希表的例子。</p>
<p><img alt="链式地址哈希表" src="../hash_collision.assets/hash_table_chaining.png" /></p>
<p align="center"> 图:链式地址哈希表 </p>
@@ -4597,8 +4597,9 @@
<li><strong>插入元素</strong>:通过哈希函数计算数组索引,若发现桶内已有元素,则从冲突位置向后线性遍历(步长通常为 <span class="arithmatex">\(1\)</span> ),直至找到空位,将元素插入其中。</li>
<li><strong>查找元素</strong>:若发现哈希冲突,则使用相同步长向后线性遍历,直到找到对应元素,返回 <code>value</code> 即可;如果遇到空位,说明目标键值对不在哈希表中,返回 <span class="arithmatex">\(\text{None}\)</span></li>
</ul>
<p><img alt="线性探测" src="../hash_collision.assets/hash_table_linear_probing.png" /></p>
<p align="center"> 图:线性探测 </p>
<p>下图展示了一个在开放寻址(线性探测)下工作的哈希表。</p>
<p><img alt="开放寻址和线性探测" src="../hash_collision.assets/hash_table_linear_probing.png" /></p>
<p align="center"> 图:开放寻址和线性探测 </p>
<p>然而,线性探测存在以下缺陷:</p>
<ul>
+5 -4
View File
@@ -3427,15 +3427,15 @@
<h1 id="61">6.1 &nbsp; 哈希表<a class="headerlink" href="#61" title="Permanent link">&para;</a></h1>
<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>如下图所示,给定 <span class="arithmatex">\(n\)</span> 个学生,每个学生都有“姓名”和“学号”两项数据。假如我们希望实现“输入一个学号,返回对应的姓名”的查询功能,则可以采用哈希表来实现。</p>
<p><img alt="哈希表的抽象表示" src="../hash_map.assets/hash_table_lookup.png" /></p>
<p align="center"> 图:哈希表的抽象表示 </p>
<p>除哈希表外,我们还可以使用数组链表实现查询功能。若将学生数据看作数组(链表)元素,则有:</p>
<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>
<p align="center"> 表:元素查询效率对比 </p>
@@ -4866,7 +4866,8 @@
<p><img alt="哈希冲突示例" src="../hash_map.assets/hash_collision.png" /></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>容易想到,哈希表容量 <span class="arithmatex">\(n\)</span> 越大,多个 <code>key</code> 被分配到同一个桶中的概率就越低,冲突就越少。因此,<strong>我们可以通过扩容哈希表来减少哈希冲突</strong></p>
<p>如下图所示,扩容前键值对 <code>(136, A)</code><code>(236, D)</code> 发生冲突,扩容后冲突消失。</p>
<p><img alt="哈希表扩容" src="../hash_map.assets/hash_table_reshash.png" /></p>
<p align="center"> 图:哈希表扩容 </p>