mirror of
https://github.com/krahets/hello-algo.git
synced 2026-07-11 15:06:07 +00:00
deploy
This commit is contained in:
@@ -3410,32 +3410,32 @@
|
||||
|
||||
<!-- Page content -->
|
||||
<h1 id="63">6.3 哈希算法<a class="headerlink" href="#63" title="Permanent link">¶</a></h1>
|
||||
<p>在上两节中,我们了解了哈希表的工作原理和哈希冲突的处理方法。然而无论是开放寻址还是链地址法,<strong>它们只能保证哈希表可以在发生冲突时正常工作,但无法减少哈希冲突的发生</strong>。</p>
|
||||
<p>如果哈希冲突过于频繁,哈希表的性能则会急剧劣化。如图 6-8 所示,对于链地址哈希表,理想情况下键值对平均分布在各个桶中,达到最佳查询效率;最差情况下所有键值对都被存储到同一个桶中,时间复杂度退化至 <span class="arithmatex">\(O(n)\)</span> 。</p>
|
||||
<p><a class="glightbox" href="../hash_algorithm.assets/hash_collision_best_worst_condition.png" data-type="image" data-width="100%" data-height="auto" data-desc-position="bottom"><img alt="哈希冲突的最佳与最差情况" class="animation-figure" src="../hash_algorithm.assets/hash_collision_best_worst_condition.png" /></a></p>
|
||||
<p align="center"> 图 6-8 哈希冲突的最佳与最差情况 </p>
|
||||
<p>前两节介绍了哈希表的工作原理和哈希冲突的处理方法。然而无论是开放寻址还是链式地址,<strong>它们只能保证哈希表可以在发生冲突时正常工作,而无法减少哈希冲突的发生</strong>。</p>
|
||||
<p>如果哈希冲突过于频繁,哈希表的性能则会急剧劣化。如图 6-8 所示,对于链式地址哈希表,理想情况下键值对均匀分布在各个桶中,达到最佳查询效率;最差情况下所有键值对都存储到同一个桶中,时间复杂度退化至 <span class="arithmatex">\(O(n)\)</span> 。</p>
|
||||
<p><a class="glightbox" href="../hash_algorithm.assets/hash_collision_best_worst_condition.png" data-type="image" data-width="100%" data-height="auto" data-desc-position="bottom"><img alt="哈希冲突的最佳情况与最差情况" class="animation-figure" src="../hash_algorithm.assets/hash_collision_best_worst_condition.png" /></a></p>
|
||||
<p align="center"> 图 6-8 哈希冲突的最佳情况与最差情况 </p>
|
||||
|
||||
<p><strong>键值对的分布情况由哈希函数决定</strong>。回忆哈希函数的计算步骤,先计算哈希值,再对数组长度取模:</p>
|
||||
<div class="highlight"><pre><span></span><code><a id="__codelineno-0-1" name="__codelineno-0-1" href="#__codelineno-0-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>capacity</code> 固定时,<strong>哈希算法 <code>hash()</code> 决定了输出值</strong>,进而决定了键值对在哈希表中的分布情况。</p>
|
||||
<p>这意味着,为了减小哈希冲突的发生概率,我们应当将注意力集中在哈希算法 <code>hash()</code> 的设计上。</p>
|
||||
<p>这意味着,为了降低哈希冲突的发生概率,我们应当将注意力集中在哈希算法 <code>hash()</code> 的设计上。</p>
|
||||
<h2 id="631">6.3.1 哈希算法的目标<a class="headerlink" href="#631" title="Permanent link">¶</a></h2>
|
||||
<p>为了实现“既快又稳”的哈希表数据结构,哈希算法应包含以下特点。</p>
|
||||
<p>为了实现“既快又稳”的哈希表数据结构,哈希算法应具备以下特点。</p>
|
||||
<ul>
|
||||
<li><strong>确定性</strong>:对于相同的输入,哈希算法应始终产生相同的输出。这样才能确保哈希表是可靠的。</li>
|
||||
<li><strong>效率高</strong>:计算哈希值的过程应该足够快。计算开销越小,哈希表的实用性越高。</li>
|
||||
<li><strong>均匀分布</strong>:哈希算法应使得键值对平均分布在哈希表中。分布越平均,哈希冲突的概率就越低。</li>
|
||||
<li><strong>均匀分布</strong>:哈希算法应使得键值对均匀分布在哈希表中。分布越均匀,哈希冲突的概率就越低。</li>
|
||||
</ul>
|
||||
<p>实际上,哈希算法除了可以用于实现哈希表,还广泛应用于其他领域中。</p>
|
||||
<ul>
|
||||
<li><strong>密码存储</strong>:为了保护用户密码的安全,系统通常不会直接存储用户的明文密码,而是存储密码的哈希值。当用户输入密码时,系统会对输入的密码计算哈希值,然后与存储的哈希值进行比较。如果两者匹配,那么密码就被视为正确。</li>
|
||||
<li><strong>数据完整性检查</strong>:数据发送方可以计算数据的哈希值并将其一同发送;接收方可以重新计算接收到的数据的哈希值,并与接收到的哈希值进行比较。如果两者匹配,那么数据就被视为完整的。</li>
|
||||
<li><strong>数据完整性检查</strong>:数据发送方可以计算数据的哈希值并将其一同发送;接收方可以重新计算接收到的数据的哈希值,并与接收到的哈希值进行比较。如果两者匹配,那么数据就被视为完整。</li>
|
||||
</ul>
|
||||
<p>对于密码学的相关应用,为了防止从哈希值推导出原始密码等逆向工程,哈希算法需要具备更高等级的安全特性。</p>
|
||||
<ul>
|
||||
<li><strong>单向性</strong>:无法通过哈希值反推出关于输入数据的任何信息。</li>
|
||||
<li><strong>抗碰撞性</strong>:应当极其困难找到两个不同的输入,使得它们的哈希值相同。</li>
|
||||
<li><strong>抗碰撞性</strong>:应当极难找到两个不同的输入,使得它们的哈希值相同。</li>
|
||||
<li><strong>雪崩效应</strong>:输入的微小变化应当导致输出的显著且不可预测的变化。</li>
|
||||
</ul>
|
||||
<p>请注意,<strong>“均匀分布”与“抗碰撞性”是两个独立的概念</strong>,满足均匀分布不一定满足抗碰撞性。例如,在随机输入 <code>key</code> 下,哈希函数 <code>key % 100</code> 可以产生均匀分布的输出。然而该哈希算法过于简单,所有后两位相等的 <code>key</code> 的输出都相同,因此我们可以很容易地从哈希值反推出可用的 <code>key</code> ,从而破解密码。</p>
|
||||
@@ -3443,7 +3443,7 @@
|
||||
<p>哈希算法的设计是一个需要考虑许多因素的复杂问题。然而对于某些要求不高的场景,我们也能设计一些简单的哈希算法。</p>
|
||||
<ul>
|
||||
<li><strong>加法哈希</strong>:对输入的每个字符的 ASCII 码进行相加,将得到的总和作为哈希值。</li>
|
||||
<li><strong>乘法哈希</strong>:利用了乘法的不相关性,每轮乘以一个常数,将各个字符的 ASCII 码累积到哈希值中。</li>
|
||||
<li><strong>乘法哈希</strong>:利用乘法的不相关性,每轮乘以一个常数,将各个字符的 ASCII 码累积到哈希值中。</li>
|
||||
<li><strong>异或哈希</strong>:将输入数据的每个元素通过异或操作累积到一个哈希值中。</li>
|
||||
<li><strong>旋转哈希</strong>:将每个字符的 ASCII 码累积到一个哈希值中,每次累积之前都会对哈希值进行旋转操作。</li>
|
||||
</ul>
|
||||
@@ -3942,8 +3942,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<p>观察发现,每种哈希算法的最后一步都是对大质数 <span class="arithmatex">\(1000000007\)</span> 取模,以确保哈希值在合适的范围内。值得思考的是,为什么要强调对质数取模,或者说对合数取模的弊端是什么?这是一个有趣的问题。</p>
|
||||
<p>先抛出结论:<strong>当我们使用大质数作为模数时,可以最大化地保证哈希值的均匀分布</strong>。因为质数不会与其他数字存在公约数,可以减少因取模操作而产生的周期性模式,从而避免哈希冲突。</p>
|
||||
<p>举个例子,假设我们选择合数 <span class="arithmatex">\(9\)</span> 作为模数,它可以被 <span class="arithmatex">\(3\)</span> 整除。那么所有可以被 <span class="arithmatex">\(3\)</span> 整除的 <code>key</code> 都会被映射到 <span class="arithmatex">\(0\)</span>、<span class="arithmatex">\(3\)</span>、<span class="arithmatex">\(6\)</span> 这三个哈希值。</p>
|
||||
<p>先抛出结论:<strong>使用大质数作为模数,可以最大化地保证哈希值的均匀分布</strong>。因为质数不与其他数字存在公约数,可以减少因取模操作而产生的周期性模式,从而避免哈希冲突。</p>
|
||||
<p>举个例子,假设我们选择合数 <span class="arithmatex">\(9\)</span> 作为模数,它可以被 <span class="arithmatex">\(3\)</span> 整除,那么所有可以被 <span class="arithmatex">\(3\)</span> 整除的 <code>key</code> 都会被映射到 <span class="arithmatex">\(0\)</span>、<span class="arithmatex">\(3\)</span>、<span class="arithmatex">\(6\)</span> 这三个哈希值。</p>
|
||||
<div class="arithmatex">\[
|
||||
\begin{aligned}
|
||||
\text{modulus} & = 9 \newline
|
||||
@@ -3951,7 +3951,7 @@
|
||||
\text{hash} & = \{ 0, 3, 6, 0, 3, 6, 0, 3, 6, 0, 3, 6,\dots \}
|
||||
\end{aligned}
|
||||
\]</div>
|
||||
<p>如果输入 <code>key</code> 恰好满足这种等差数列的数据分布,那么哈希值就会出现聚堆,从而加重哈希冲突。现在,假设将 <code>modulus</code> 替换为质数 <span class="arithmatex">\(13\)</span> ,由于 <code>key</code> 和 <code>modulus</code> 之间不存在公约数,输出的哈希值的均匀性会明显提升。</p>
|
||||
<p>如果输入 <code>key</code> 恰好满足这种等差数列的数据分布,那么哈希值就会出现聚堆,从而加重哈希冲突。现在,假设将 <code>modulus</code> 替换为质数 <span class="arithmatex">\(13\)</span> ,由于 <code>key</code> 和 <code>modulus</code> 之间不存在公约数,因此输出的哈希值的均匀性会明显提升。</p>
|
||||
<div class="arithmatex">\[
|
||||
\begin{aligned}
|
||||
\text{modulus} & = 13 \newline
|
||||
@@ -3959,15 +3959,15 @@
|
||||
\text{hash} & = \{ 0, 3, 6, 9, 12, 2, 5, 8, 11, 1, 4, 7, \dots \}
|
||||
\end{aligned}
|
||||
\]</div>
|
||||
<p>值得说明的是,如果能够保证 <code>key</code> 是随机均匀分布的,那么选择质数或者合数作为模数都是可以的,它们都能输出均匀分布的哈希值。而当 <code>key</code> 的分布存在某种周期性时,对合数取模更容易出现聚集现象。</p>
|
||||
<p>值得说明的是,如果能够保证 <code>key</code> 是随机均匀分布的,那么选择质数或者合数作为模数都可以,它们都能输出均匀分布的哈希值。而当 <code>key</code> 的分布存在某种周期性时,对合数取模更容易出现聚集现象。</p>
|
||||
<p>总而言之,我们通常选取质数作为模数,并且这个质数最好足够大,以尽可能消除周期性模式,提升哈希算法的稳健性。</p>
|
||||
<h2 id="633">6.3.3 常见哈希算法<a class="headerlink" href="#633" title="Permanent link">¶</a></h2>
|
||||
<p>不难发现,以上介绍的简单哈希算法都比较“脆弱”,远远没有达到哈希算法的设计目标。例如,由于加法和异或满足交换律,因此加法哈希和异或哈希无法区分内容相同但顺序不同的字符串,这可能会加剧哈希冲突,并引起一些安全问题。</p>
|
||||
<p>在实际中,我们通常会用一些标准哈希算法,例如 MD5、SHA-1、SHA-2、SHA3 等。它们可以将任意长度的输入数据映射到恒定长度的哈希值。</p>
|
||||
<p>在实际中,我们通常会用一些标准哈希算法,例如 MD5、SHA-1、SHA-2、SHA-3 等。它们可以将任意长度的输入数据映射到恒定长度的哈希值。</p>
|
||||
<p>近一个世纪以来,哈希算法处在不断升级与优化的过程中。一部分研究人员努力提升哈希算法的性能,另一部分研究人员和黑客则致力于寻找哈希算法的安全性问题。表 6-2 展示了在实际应用中常见的哈希算法。</p>
|
||||
<ul>
|
||||
<li>MD5 和 SHA-1 已多次被成功攻击,因此它们被各类安全应用弃用。</li>
|
||||
<li>SHA-2 系列中的 SHA-256 是最安全的哈希算法之一,仍未出现成功的攻击案例,因此常被用在各类安全应用与协议中。</li>
|
||||
<li>SHA-2 系列中的 SHA-256 是最安全的哈希算法之一,仍未出现成功的攻击案例,因此常用在各类安全应用与协议中。</li>
|
||||
<li>SHA-3 相较 SHA-2 的实现开销更低、计算效率更高,但目前使用覆盖度不如 SHA-2 系列。</li>
|
||||
</ul>
|
||||
<p align="center"> 表 6-2 常见的哈希算法 </p>
|
||||
@@ -3995,7 +3995,7 @@
|
||||
<td>输出长度</td>
|
||||
<td>128 bits</td>
|
||||
<td>160 bits</td>
|
||||
<td>256 / 512 bits</td>
|
||||
<td>256/512 bits</td>
|
||||
<td>224/256/384/512 bits</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -4026,7 +4026,7 @@
|
||||
<p>我们知道,哈希表的 <code>key</code> 可以是整数、小数或字符串等数据类型。编程语言通常会为这些数据类型提供内置的哈希算法,用于计算哈希表中的桶索引。以 Python 为例,我们可以调用 <code>hash()</code> 函数来计算各种数据类型的哈希值。</p>
|
||||
<ul>
|
||||
<li>整数和布尔量的哈希值就是其本身。</li>
|
||||
<li>浮点数和字符串的哈希值计算较为复杂,有兴趣的同学请自行学习。</li>
|
||||
<li>浮点数和字符串的哈希值计算较为复杂,有兴趣的读者请自行学习。</li>
|
||||
<li>元组的哈希值是对其中每一个元素进行哈希,然后将这些哈希值组合起来,得到单一的哈希值。</li>
|
||||
<li>对象的哈希值基于其内存地址生成。通过重写对象的哈希方法,可实现基于内容生成哈希值。</li>
|
||||
</ul>
|
||||
@@ -4051,7 +4051,7 @@
|
||||
<a id="__codelineno-13-12" name="__codelineno-13-12" href="#__codelineno-13-12"></a>
|
||||
<a id="__codelineno-13-13" name="__codelineno-13-13" href="#__codelineno-13-13"></a><span class="nb">str</span> <span class="o">=</span> <span class="s2">"Hello 算法"</span>
|
||||
<a id="__codelineno-13-14" name="__codelineno-13-14" href="#__codelineno-13-14"></a><span class="n">hash_str</span> <span class="o">=</span> <span class="nb">hash</span><span class="p">(</span><span class="nb">str</span><span class="p">)</span>
|
||||
<a id="__codelineno-13-15" name="__codelineno-13-15" href="#__codelineno-13-15"></a><span class="c1"># 字符串 Hello 算法 的哈希值为 4617003410720528961</span>
|
||||
<a id="__codelineno-13-15" name="__codelineno-13-15" href="#__codelineno-13-15"></a><span class="c1"># 字符串“Hello 算法”的哈希值为 4617003410720528961</span>
|
||||
<a id="__codelineno-13-16" name="__codelineno-13-16" href="#__codelineno-13-16"></a>
|
||||
<a id="__codelineno-13-17" name="__codelineno-13-17" href="#__codelineno-13-17"></a><span class="n">tup</span> <span class="o">=</span> <span class="p">(</span><span class="mi">12836</span><span class="p">,</span> <span class="s2">"小哈"</span><span class="p">)</span>
|
||||
<a id="__codelineno-13-18" name="__codelineno-13-18" href="#__codelineno-13-18"></a><span class="n">hash_tup</span> <span class="o">=</span> <span class="nb">hash</span><span class="p">(</span><span class="n">tup</span><span class="p">)</span>
|
||||
@@ -4077,7 +4077,7 @@
|
||||
<a id="__codelineno-14-12" name="__codelineno-14-12" href="#__codelineno-14-12"></a>
|
||||
<a id="__codelineno-14-13" name="__codelineno-14-13" href="#__codelineno-14-13"></a><span class="n">string</span><span class="w"> </span><span class="n">str</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">"Hello 算法"</span><span class="p">;</span>
|
||||
<a id="__codelineno-14-14" name="__codelineno-14-14" href="#__codelineno-14-14"></a><span class="kt">size_t</span><span class="w"> </span><span class="n">hashStr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">hash</span><span class="o"><</span><span class="n">string</span><span class="o">></span><span class="p">()(</span><span class="n">str</span><span class="p">);</span>
|
||||
<a id="__codelineno-14-15" name="__codelineno-14-15" href="#__codelineno-14-15"></a><span class="c1">// 字符串 Hello 算法 的哈希值为 15466937326284535026</span>
|
||||
<a id="__codelineno-14-15" name="__codelineno-14-15" href="#__codelineno-14-15"></a><span class="c1">// 字符串“Hello 算法”的哈希值为 15466937326284535026</span>
|
||||
<a id="__codelineno-14-16" name="__codelineno-14-16" href="#__codelineno-14-16"></a>
|
||||
<a id="__codelineno-14-17" name="__codelineno-14-17" href="#__codelineno-14-17"></a><span class="c1">// 在 C++ 中,内置 std:hash() 仅提供基本数据类型的哈希值计算</span>
|
||||
<a id="__codelineno-14-18" name="__codelineno-14-18" href="#__codelineno-14-18"></a><span class="c1">// 数组、对象的哈希值计算需要自行实现</span>
|
||||
@@ -4098,7 +4098,7 @@
|
||||
<a id="__codelineno-15-12" name="__codelineno-15-12" href="#__codelineno-15-12"></a>
|
||||
<a id="__codelineno-15-13" name="__codelineno-15-13" href="#__codelineno-15-13"></a><span class="n">String</span><span class="w"> </span><span class="n">str</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">"Hello 算法"</span><span class="p">;</span>
|
||||
<a id="__codelineno-15-14" name="__codelineno-15-14" href="#__codelineno-15-14"></a><span class="kt">int</span><span class="w"> </span><span class="n">hashStr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">str</span><span class="p">.</span><span class="na">hashCode</span><span class="p">();</span>
|
||||
<a id="__codelineno-15-15" name="__codelineno-15-15" href="#__codelineno-15-15"></a><span class="c1">// 字符串 Hello 算法 的哈希值为 -727081396</span>
|
||||
<a id="__codelineno-15-15" name="__codelineno-15-15" href="#__codelineno-15-15"></a><span class="c1">// 字符串“Hello 算法”的哈希值为 -727081396</span>
|
||||
<a id="__codelineno-15-16" name="__codelineno-15-16" href="#__codelineno-15-16"></a>
|
||||
<a id="__codelineno-15-17" name="__codelineno-15-17" href="#__codelineno-15-17"></a><span class="n">Object</span><span class="o">[]</span><span class="w"> </span><span class="n">arr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">12836</span><span class="p">,</span><span class="w"> </span><span class="s">"小哈"</span><span class="w"> </span><span class="p">};</span>
|
||||
<a id="__codelineno-15-18" name="__codelineno-15-18" href="#__codelineno-15-18"></a><span class="kt">int</span><span class="w"> </span><span class="n">hashTup</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Arrays</span><span class="p">.</span><span class="na">hashCode</span><span class="p">(</span><span class="n">arr</span><span class="p">);</span>
|
||||
@@ -4124,7 +4124,7 @@
|
||||
<a id="__codelineno-16-12" name="__codelineno-16-12" href="#__codelineno-16-12"></a>
|
||||
<a id="__codelineno-16-13" name="__codelineno-16-13" href="#__codelineno-16-13"></a><span class="kt">string</span><span class="w"> </span><span class="n">str</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">"Hello 算法"</span><span class="p">;</span>
|
||||
<a id="__codelineno-16-14" name="__codelineno-16-14" href="#__codelineno-16-14"></a><span class="kt">int</span><span class="w"> </span><span class="n">hashStr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">str</span><span class="p">.</span><span class="n">GetHashCode</span><span class="p">();</span>
|
||||
<a id="__codelineno-16-15" name="__codelineno-16-15" href="#__codelineno-16-15"></a><span class="c1">// 字符串 Hello 算法 的哈希值为 -586107568;</span>
|
||||
<a id="__codelineno-16-15" name="__codelineno-16-15" href="#__codelineno-16-15"></a><span class="c1">// 字符串“Hello 算法”的哈希值为 -586107568;</span>
|
||||
<a id="__codelineno-16-16" name="__codelineno-16-16" href="#__codelineno-16-16"></a>
|
||||
<a id="__codelineno-16-17" name="__codelineno-16-17" href="#__codelineno-16-17"></a><span class="kt">object</span><span class="p">[]</span><span class="w"> </span><span class="n">arr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="m">12836</span><span class="p">,</span><span class="w"> </span><span class="s">"小哈"</span><span class="p">];</span>
|
||||
<a id="__codelineno-16-18" name="__codelineno-16-18" href="#__codelineno-16-18"></a><span class="kt">int</span><span class="w"> </span><span class="n">hashTup</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">arr</span><span class="p">.</span><span class="n">GetHashCode</span><span class="p">();</span>
|
||||
@@ -4154,7 +4154,7 @@
|
||||
<a id="__codelineno-18-12" name="__codelineno-18-12" href="#__codelineno-18-12"></a>
|
||||
<a id="__codelineno-18-13" name="__codelineno-18-13" href="#__codelineno-18-13"></a><span class="kd">let</span> <span class="nv">str</span> <span class="p">=</span> <span class="s">"Hello 算法"</span>
|
||||
<a id="__codelineno-18-14" name="__codelineno-18-14" href="#__codelineno-18-14"></a><span class="kd">let</span> <span class="nv">hashStr</span> <span class="p">=</span> <span class="n">str</span><span class="p">.</span><span class="n">hashValue</span>
|
||||
<a id="__codelineno-18-15" name="__codelineno-18-15" href="#__codelineno-18-15"></a><span class="c1">// 字符串 Hello 算法 的哈希值为 -7850626797806988787</span>
|
||||
<a id="__codelineno-18-15" name="__codelineno-18-15" href="#__codelineno-18-15"></a><span class="c1">// 字符串“Hello 算法”的哈希值为 -7850626797806988787</span>
|
||||
<a id="__codelineno-18-16" name="__codelineno-18-16" href="#__codelineno-18-16"></a>
|
||||
<a id="__codelineno-18-17" name="__codelineno-18-17" href="#__codelineno-18-17"></a><span class="kd">let</span> <span class="nv">arr</span> <span class="p">=</span> <span class="p">[</span><span class="n">AnyHashable</span><span class="p">(</span><span class="mi">12836</span><span class="p">),</span> <span class="n">AnyHashable</span><span class="p">(</span><span class="s">"小哈"</span><span class="p">)]</span>
|
||||
<a id="__codelineno-18-18" name="__codelineno-18-18" href="#__codelineno-18-18"></a><span class="kd">let</span> <span class="nv">hashTup</span> <span class="p">=</span> <span class="n">arr</span><span class="p">.</span><span class="n">hashValue</span>
|
||||
@@ -4188,7 +4188,7 @@
|
||||
<a id="__codelineno-21-12" name="__codelineno-21-12" href="#__codelineno-21-12"></a>
|
||||
<a id="__codelineno-21-13" name="__codelineno-21-13" href="#__codelineno-21-13"></a><span class="kt">String</span><span class="w"> </span><span class="n">str</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Hello 算法"</span><span class="p">;</span>
|
||||
<a id="__codelineno-21-14" name="__codelineno-21-14" href="#__codelineno-21-14"></a><span class="kt">int</span><span class="w"> </span><span class="n">hashStr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">str</span><span class="p">.</span><span class="n">hashCode</span><span class="p">;</span>
|
||||
<a id="__codelineno-21-15" name="__codelineno-21-15" href="#__codelineno-21-15"></a><span class="c1">// 字符串 Hello 算法 的哈希值为 468167534</span>
|
||||
<a id="__codelineno-21-15" name="__codelineno-21-15" href="#__codelineno-21-15"></a><span class="c1">// 字符串“Hello 算法”的哈希值为 468167534</span>
|
||||
<a id="__codelineno-21-16" name="__codelineno-21-16" href="#__codelineno-21-16"></a>
|
||||
<a id="__codelineno-21-17" name="__codelineno-21-17" href="#__codelineno-21-17"></a><span class="n">List</span><span class="w"> </span><span class="n">arr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="m">12836</span><span class="p">,</span><span class="w"> </span><span class="s2">"小哈"</span><span class="p">];</span>
|
||||
<a id="__codelineno-21-18" name="__codelineno-21-18" href="#__codelineno-21-18"></a><span class="kt">int</span><span class="w"> </span><span class="n">hashArr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">arr</span><span class="p">.</span><span class="n">hashCode</span><span class="p">;</span>
|
||||
@@ -4226,7 +4226,7 @@
|
||||
<a id="__codelineno-22-24" name="__codelineno-22-24" href="#__codelineno-22-24"></a><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">str_hasher</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">DefaultHasher</span>::<span class="n">new</span><span class="p">();</span>
|
||||
<a id="__codelineno-22-25" name="__codelineno-22-25" href="#__codelineno-22-25"></a><span class="kt">str</span><span class="p">.</span><span class="n">hash</span><span class="p">(</span><span class="o">&</span><span class="k">mut</span><span class="w"> </span><span class="n">str_hasher</span><span class="p">);</span>
|
||||
<a id="__codelineno-22-26" name="__codelineno-22-26" href="#__codelineno-22-26"></a><span class="kd">let</span><span class="w"> </span><span class="n">hash_str</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">str_hasher</span><span class="p">.</span><span class="n">finish</span><span class="p">();</span>
|
||||
<a id="__codelineno-22-27" name="__codelineno-22-27" href="#__codelineno-22-27"></a><span class="c1">// 字符串 Hello 算法 的哈希值为 16092673739211250988</span>
|
||||
<a id="__codelineno-22-27" name="__codelineno-22-27" href="#__codelineno-22-27"></a><span class="c1">// 字符串“Hello 算法”的哈希值为 16092673739211250988</span>
|
||||
<a id="__codelineno-22-28" name="__codelineno-22-28" href="#__codelineno-22-28"></a>
|
||||
<a id="__codelineno-22-29" name="__codelineno-22-29" href="#__codelineno-22-29"></a><span class="kd">let</span><span class="w"> </span><span class="n">arr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="o">&</span><span class="mi">12836</span><span class="p">,</span><span class="w"> </span><span class="o">&</span><span class="s">"小哈"</span><span class="p">);</span>
|
||||
<a id="__codelineno-22-30" name="__codelineno-22-30" href="#__codelineno-22-30"></a><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">tup_hasher</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">DefaultHasher</span>::<span class="n">new</span><span class="p">();</span>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
@@ -3450,10 +3450,10 @@
|
||||
|
||||
<!-- Page content -->
|
||||
<h1 id="62">6.2 哈希冲突<a class="headerlink" href="#62" title="Permanent link">¶</a></h1>
|
||||
<p>上节提到,<strong>通常情况下哈希函数的输入空间远大于输出空间</strong>,因此理论上哈希冲突是不可避免的。比如,输入空间为全体整数,输出空间为数组容量大小,则必然有多个整数映射至同一桶索引。</p>
|
||||
<p>哈希冲突会导致查询结果错误,严重影响哈希表的可用性。为解决该问题,我们可以每当遇到哈希冲突时就进行哈希表扩容,直至冲突消失为止。此方法简单粗暴且有效,但效率太低,因为哈希表扩容需要进行大量的数据搬运与哈希值计算。为了提升效率,我们可以采用以下策略。</p>
|
||||
<p>上一节提到,<strong>通常情况下哈希函数的输入空间远大于输出空间</strong>,因此理论上哈希冲突是不可避免的。比如,输入空间为全体整数,输出空间为数组容量大小,则必然有多个整数映射至同一桶索引。</p>
|
||||
<p>哈希冲突会导致查询结果错误,严重影响哈希表的可用性。为解决该问题,我们可以每当遇到哈希冲突就进行哈希表扩容,直至冲突消失为止。此方法简单粗暴且有效,但效率太低,因为哈希表扩容需要进行大量的数据搬运与哈希值计算。为了提升效率,我们可以采用以下策略。</p>
|
||||
<ol>
|
||||
<li>改良哈希表数据结构,<strong>使得哈希表可以在存在哈希冲突时正常工作</strong>。</li>
|
||||
<li>改良哈希表数据结构,<strong>使得哈希表可以在出现哈希冲突时正常工作</strong>。</li>
|
||||
<li>仅在必要时,即当哈希冲突比较严重时,才执行扩容操作。</li>
|
||||
</ol>
|
||||
<p>哈希表的结构改良方法主要包括“链式地址”和“开放寻址”。</p>
|
||||
@@ -3465,8 +3465,8 @@
|
||||
<p>基于链式地址实现的哈希表的操作方法发生了以下变化。</p>
|
||||
<ul>
|
||||
<li><strong>查询元素</strong>:输入 <code>key</code> ,经过哈希函数得到桶索引,即可访问链表头节点,然后遍历链表并对比 <code>key</code> 以查找目标键值对。</li>
|
||||
<li><strong>添加元素</strong>:先通过哈希函数访问链表头节点,然后将节点(即键值对)添加到链表中。</li>
|
||||
<li><strong>删除元素</strong>:根据哈希函数的结果访问链表头部,接着遍历链表以查找目标节点,并将其删除。</li>
|
||||
<li><strong>添加元素</strong>:首先通过哈希函数访问链表头节点,然后将节点(键值对)添加到链表中。</li>
|
||||
<li><strong>删除元素</strong>:根据哈希函数的结果访问链表头部,接着遍历链表以查找目标节点并将其删除。</li>
|
||||
</ul>
|
||||
<p>链式地址存在以下局限性。</p>
|
||||
<ul>
|
||||
@@ -3476,7 +3476,7 @@
|
||||
<p>以下代码给出了链式地址哈希表的简单实现,需要注意两点。</p>
|
||||
<ul>
|
||||
<li>使用列表(动态数组)代替链表,从而简化代码。在这种设定下,哈希表(数组)包含多个桶,每个桶都是一个列表。</li>
|
||||
<li>以下实现包含哈希表扩容方法。当负载因子超过 <span class="arithmatex">\(\frac{2}{3}\)</span> 时,我们将哈希表扩容至 <span class="arithmatex">\(2\)</span> 倍。</li>
|
||||
<li>以下实现包含哈希表扩容方法。当负载因子超过 <span class="arithmatex">\(\frac{2}{3}\)</span> 时,我们将哈希表扩容至原先的 <span class="arithmatex">\(2\)</span> 倍。</li>
|
||||
</ul>
|
||||
<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">Python</label><label for="__tabbed_1_2">C++</label><label for="__tabbed_1_3">Java</label><label for="__tabbed_1_4">C#</label><label for="__tabbed_1_5">Go</label><label for="__tabbed_1_6">Swift</label><label for="__tabbed_1_7">JS</label><label for="__tabbed_1_8">TS</label><label for="__tabbed_1_9">Dart</label><label for="__tabbed_1_10">Rust</label><label for="__tabbed_1_11">C</label><label for="__tabbed_1_12">Zig</label></div>
|
||||
<div class="tabbed-content">
|
||||
@@ -4654,7 +4654,7 @@
|
||||
<a id="__codelineno-10-82" name="__codelineno-10-82" href="#__codelineno-10-82"></a><span class="w"> </span><span class="p">}</span>
|
||||
<a id="__codelineno-10-83" name="__codelineno-10-83" href="#__codelineno-10-83"></a><span class="w"> </span><span class="n">cur</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">cur</span><span class="o">-></span><span class="n">next</span><span class="p">;</span>
|
||||
<a id="__codelineno-10-84" name="__codelineno-10-84" href="#__codelineno-10-84"></a><span class="w"> </span><span class="p">}</span>
|
||||
<a id="__codelineno-10-85" name="__codelineno-10-85" href="#__codelineno-10-85"></a><span class="w"> </span><span class="c1">// 若无该 key ,则将键值对添加至尾部</span>
|
||||
<a id="__codelineno-10-85" name="__codelineno-10-85" href="#__codelineno-10-85"></a><span class="w"> </span><span class="c1">// 若无该 key ,则将键值对添加至链表头部</span>
|
||||
<a id="__codelineno-10-86" name="__codelineno-10-86" href="#__codelineno-10-86"></a><span class="w"> </span><span class="n">Pair</span><span class="w"> </span><span class="o">*</span><span class="n">newPair</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">Pair</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="n">malloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="n">Pair</span><span class="p">));</span>
|
||||
<a id="__codelineno-10-87" name="__codelineno-10-87" href="#__codelineno-10-87"></a><span class="w"> </span><span class="n">newPair</span><span class="o">-></span><span class="n">key</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">key</span><span class="p">;</span>
|
||||
<a id="__codelineno-10-88" name="__codelineno-10-88" href="#__codelineno-10-88"></a><span class="w"> </span><span class="n">strcpy</span><span class="p">(</span><span class="n">newPair</span><span class="o">-></span><span class="n">val</span><span class="p">,</span><span class="w"> </span><span class="n">val</span><span class="p">);</span>
|
||||
@@ -4740,7 +4740,7 @@
|
||||
<p>值得注意的是,当链表很长时,查询效率 <span class="arithmatex">\(O(n)\)</span> 很差。<strong>此时可以将链表转换为“AVL 树”或“红黑树”</strong>,从而将查询操作的时间复杂度优化至 <span class="arithmatex">\(O(\log n)\)</span> 。</p>
|
||||
<h2 id="622">6.2.2 开放寻址<a class="headerlink" href="#622" title="Permanent link">¶</a></h2>
|
||||
<p>「开放寻址 open addressing」不引入额外的数据结构,而是通过“多次探测”来处理哈希冲突,探测方式主要包括线性探测、平方探测、多次哈希等。</p>
|
||||
<p>下面将主要以线性探测为例,介绍开放寻址哈希表的工作机制与代码实现。</p>
|
||||
<p>下面以线性探测为例,介绍开放寻址哈希表的工作机制。</p>
|
||||
<h3 id="1">1. 线性探测<a class="headerlink" href="#1" title="Permanent link">¶</a></h3>
|
||||
<p>线性探测采用固定步长的线性搜索来进行探测,其操作方法与普通哈希表有所不同。</p>
|
||||
<ul>
|
||||
@@ -4759,7 +4759,7 @@
|
||||
<p>为了解决该问题,我们可以采用「懒删除 lazy deletion」机制:它不直接从哈希表中移除元素,<strong>而是利用一个常量 <code>TOMBSTONE</code> 来标记这个桶</strong>。在该机制下,<span class="arithmatex">\(\text{None}\)</span> 和 <code>TOMBSTONE</code> 都代表空桶,都可以放置键值对。但不同的是,线性探测到 <code>TOMBSTONE</code> 时应该继续遍历,因为其之下可能还存在键值对。</p>
|
||||
<p>然而,<strong>懒删除可能会加速哈希表的性能退化</strong>。这是因为每次删除操作都会产生一个删除标记,随着 <code>TOMBSTONE</code> 的增加,搜索时间也会增加,因为线性探测可能需要跳过多个 <code>TOMBSTONE</code> 才能找到目标元素。</p>
|
||||
<p>为此,考虑在线性探测中记录遇到的首个 <code>TOMBSTONE</code> 的索引,并将搜索到的目标元素与该 <code>TOMBSTONE</code> 交换位置。这样做的好处是当每次查询或添加元素时,元素会被移动至距离理想位置(探测起始点)更近的桶,从而优化查询效率。</p>
|
||||
<p>以下代码实现了一个包含懒删除的开放寻址(线性探测)哈希表。为了更加充分地使用哈希表的空间,我们将哈希表看作是一个“环形数组”,当越过数组尾部时,回到头部继续遍历。</p>
|
||||
<p>以下代码实现了一个包含懒删除的开放寻址(线性探测)哈希表。为了更加充分地使用哈希表的空间,我们将哈希表看作一个“环形数组”,当越过数组尾部时,回到头部继续遍历。</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">Python</label><label for="__tabbed_2_2">C++</label><label for="__tabbed_2_3">Java</label><label for="__tabbed_2_4">C#</label><label for="__tabbed_2_5">Go</label><label for="__tabbed_2_6">Swift</label><label for="__tabbed_2_7">JS</label><label for="__tabbed_2_8">TS</label><label for="__tabbed_2_9">Dart</label><label for="__tabbed_2_10">Rust</label><label for="__tabbed_2_11">C</label><label for="__tabbed_2_12">Zig</label></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
@@ -5293,7 +5293,7 @@
|
||||
<a id="__codelineno-16-40" name="__codelineno-16-40" href="#__codelineno-16-40"></a><span class="w"> </span><span class="c1">// 线性探测,从 index 开始向后遍历</span>
|
||||
<a id="__codelineno-16-41" name="__codelineno-16-41" href="#__codelineno-16-41"></a><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p"><</span><span class="w"> </span><span class="nx">m</span><span class="p">.</span><span class="nx">capacity</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span>
|
||||
<a id="__codelineno-16-42" name="__codelineno-16-42" href="#__codelineno-16-42"></a><span class="w"> </span><span class="c1">// 计算桶索引,越过尾部返回头部</span>
|
||||
<a id="__codelineno-16-43" name="__codelineno-16-43" href="#__codelineno-16-43"></a><span class="w"> </span><span class="nx">j</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="p">(</span><span class="nx">idx</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="nx">m</span><span class="p">.</span><span class="nx">capacity</span>
|
||||
<a id="__codelineno-16-43" name="__codelineno-16-43" href="#__codelineno-16-43"></a><span class="w"> </span><span class="nx">j</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="p">(</span><span class="nx">idx</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">i</span><span class="p">)</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="nx">m</span><span class="p">.</span><span class="nx">capacity</span>
|
||||
<a id="__codelineno-16-44" name="__codelineno-16-44" href="#__codelineno-16-44"></a><span class="w"> </span><span class="c1">// 若遇到空桶,说明无此 key ,则返回 null</span>
|
||||
<a id="__codelineno-16-45" name="__codelineno-16-45" href="#__codelineno-16-45"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="nx">m</span><span class="p">.</span><span class="nx">buckets</span><span class="p">[</span><span class="nx">j</span><span class="p">]</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="p">(</span><span class="nx">pair</span><span class="p">{})</span><span class="w"> </span><span class="p">{</span>
|
||||
<a id="__codelineno-16-46" name="__codelineno-16-46" href="#__codelineno-16-46"></a><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">""</span>
|
||||
@@ -5341,7 +5341,7 @@
|
||||
<a id="__codelineno-16-88" name="__codelineno-16-88" href="#__codelineno-16-88"></a><span class="w"> </span><span class="c1">// 线性探测,从 index 开始向后遍历</span>
|
||||
<a id="__codelineno-16-89" name="__codelineno-16-89" href="#__codelineno-16-89"></a><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p"><</span><span class="w"> </span><span class="nx">m</span><span class="p">.</span><span class="nx">capacity</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span>
|
||||
<a id="__codelineno-16-90" name="__codelineno-16-90" href="#__codelineno-16-90"></a><span class="w"> </span><span class="c1">// 计算桶索引,越过尾部返回头部</span>
|
||||
<a id="__codelineno-16-91" name="__codelineno-16-91" href="#__codelineno-16-91"></a><span class="w"> </span><span class="nx">j</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="p">(</span><span class="nx">idx</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="nx">m</span><span class="p">.</span><span class="nx">capacity</span>
|
||||
<a id="__codelineno-16-91" name="__codelineno-16-91" href="#__codelineno-16-91"></a><span class="w"> </span><span class="nx">j</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="p">(</span><span class="nx">idx</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">i</span><span class="p">)</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="nx">m</span><span class="p">.</span><span class="nx">capacity</span>
|
||||
<a id="__codelineno-16-92" name="__codelineno-16-92" href="#__codelineno-16-92"></a><span class="w"> </span><span class="c1">// 若遇到空桶,说明无此 key ,则直接返回</span>
|
||||
<a id="__codelineno-16-93" name="__codelineno-16-93" href="#__codelineno-16-93"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="nx">m</span><span class="p">.</span><span class="nx">buckets</span><span class="p">[</span><span class="nx">j</span><span class="p">]</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="p">(</span><span class="nx">pair</span><span class="p">{})</span><span class="w"> </span><span class="p">{</span>
|
||||
<a id="__codelineno-16-94" name="__codelineno-16-94" href="#__codelineno-16-94"></a><span class="w"> </span><span class="k">return</span>
|
||||
@@ -6240,12 +6240,12 @@
|
||||
<li>由于平方的增长,平方探测可能不会探测整个哈希表,这意味着即使哈希表中有空桶,平方探测也可能无法访问到它。</li>
|
||||
</ul>
|
||||
<h3 id="3">3. 多次哈希<a class="headerlink" href="#3" title="Permanent link">¶</a></h3>
|
||||
<p>多次哈希使用多个哈希函数 <span class="arithmatex">\(f_1(x)\)</span>、<span class="arithmatex">\(f_2(x)\)</span>、<span class="arithmatex">\(f_3(x)\)</span>、<span class="arithmatex">\(\dots\)</span> 进行探测。</p>
|
||||
<p>顾名思义,多次哈希方法使用多个哈希函数 <span class="arithmatex">\(f_1(x)\)</span>、<span class="arithmatex">\(f_2(x)\)</span>、<span class="arithmatex">\(f_3(x)\)</span>、<span class="arithmatex">\(\dots\)</span> 进行探测。</p>
|
||||
<ul>
|
||||
<li><strong>插入元素</strong>:若哈希函数 <span class="arithmatex">\(f_1(x)\)</span> 出现冲突,则尝试 <span class="arithmatex">\(f_2(x)\)</span> ,以此类推,直到找到空桶后插入元素。</li>
|
||||
<li><strong>查找元素</strong>:在相同的哈希函数顺序下进行查找,直到找到目标元素时返回;或当遇到空桶或已尝试所有哈希函数,说明哈希表中不存在该元素,则返回 <span class="arithmatex">\(\text{None}\)</span> 。</li>
|
||||
<li><strong>查找元素</strong>:在相同的哈希函数顺序下进行查找,直到找到目标元素时返回;若遇到空桶或已尝试所有哈希函数,说明哈希表中不存在该元素,则返回 <span class="arithmatex">\(\text{None}\)</span> 。</li>
|
||||
</ul>
|
||||
<p>与线性探测相比,多次哈希方法不易产生聚集,但多个哈希函数会增加额外的计算量。</p>
|
||||
<p>与线性探测相比,多次哈希方法不易产生聚集,但多个哈希函数会带来额外的计算量。</p>
|
||||
<div class="admonition tip">
|
||||
<p class="admonition-title">Tip</p>
|
||||
<p>请注意,开放寻址(线性探测、平方探测和多次哈希)哈希表都存在“不能直接删除元素”的问题。</p>
|
||||
@@ -6253,9 +6253,9 @@
|
||||
<h2 id="623">6.2.3 编程语言的选择<a class="headerlink" href="#623" title="Permanent link">¶</a></h2>
|
||||
<p>各个编程语言采取了不同的哈希表实现策略,以下举几个例子。</p>
|
||||
<ul>
|
||||
<li>Java 采用链式地址。自 JDK 1.8 以来,当 HashMap 内数组长度达到 64 且链表长度达到 8 时,链表会被转换为红黑树以提升查找性能。</li>
|
||||
<li>Python 采用开放寻址。字典 dict 使用伪随机数进行探测。</li>
|
||||
<li>Golang 采用链式地址。Go 规定每个桶最多存储 8 个键值对,超出容量则连接一个溢出桶。当溢出桶过多时,会执行一次特殊的等量扩容操作,以确保性能。</li>
|
||||
<li>Java 采用链式地址。自 JDK 1.8 以来,当 HashMap 内数组长度达到 64 且链表长度达到 8 时,链表会转换为红黑树以提升查找性能。</li>
|
||||
<li>Go 采用链式地址。Go 规定每个桶最多存储 8 个键值对,超出容量则连接一个溢出桶。当溢出桶过多时,会执行一次特殊的等量扩容操作,以确保性能。</li>
|
||||
</ul>
|
||||
|
||||
<!-- Source file information -->
|
||||
|
||||
@@ -3443,7 +3443,7 @@
|
||||
</div>
|
||||
<p>观察发现,<strong>在哈希表中进行增删查改的时间复杂度都是 <span class="arithmatex">\(O(1)\)</span></strong> ,非常高效。</p>
|
||||
<h2 id="611">6.1.1 哈希表常用操作<a class="headerlink" href="#611" title="Permanent link">¶</a></h2>
|
||||
<p>哈希表的常见操作包括:初始化、查询操作、添加键值对和删除键值对等。</p>
|
||||
<p>哈希表的常见操作包括:初始化、查询操作、添加键值对和删除键值对等,示例代码如下:</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">Python</label><label for="__tabbed_1_2">C++</label><label for="__tabbed_1_3">Java</label><label for="__tabbed_1_4">C#</label><label for="__tabbed_1_5">Go</label><label for="__tabbed_1_6">Swift</label><label for="__tabbed_1_7">JS</label><label for="__tabbed_1_8">TS</label><label for="__tabbed_1_9">Dart</label><label for="__tabbed_1_10">Rust</label><label for="__tabbed_1_11">C</label><label for="__tabbed_1_12">Zig</label></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
@@ -3671,7 +3671,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>哈希表有三种常用遍历方式:遍历键值对、遍历键和遍历值。</p>
|
||||
<p>哈希表有三种常用的遍历方式:遍历键值对、遍历键和遍历值。示例代码如下:</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">Python</label><label for="__tabbed_2_2">C++</label><label for="__tabbed_2_3">Java</label><label for="__tabbed_2_4">C#</label><label for="__tabbed_2_5">Go</label><label for="__tabbed_2_6">Swift</label><label for="__tabbed_2_7">JS</label><label for="__tabbed_2_8">TS</label><label for="__tabbed_2_9">Dart</label><label for="__tabbed_2_10">Rust</label><label for="__tabbed_2_11">C</label><label for="__tabbed_2_12">Zig</label></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
@@ -3843,7 +3843,7 @@
|
||||
</div>
|
||||
<h2 id="612">6.1.2 哈希表简单实现<a class="headerlink" href="#612" title="Permanent link">¶</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> 定位对应的桶呢?这是通过「哈希函数 hash function」实现的。哈希函数的作用是将一个较大的输入空间映射到一个较小的输出空间。在哈希表中,输入空间是所有 <code>key</code> ,输出空间是所有桶(数组索引)。换句话说,输入一个 <code>key</code> ,<strong>我们可以通过哈希函数得到该 <code>key</code> 对应的键值对在数组中的存储位置</strong>。</p>
|
||||
<p>输入一个 <code>key</code> ,哈希函数的计算过程分为以下两步。</p>
|
||||
<ol>
|
||||
<li>通过某种哈希算法 <code>hash()</code> 计算得到哈希值。</li>
|
||||
@@ -3868,7 +3868,7 @@
|
||||
<a id="__codelineno-25-6" name="__codelineno-25-6" href="#__codelineno-25-6"></a> <span class="bp">self</span><span class="o">.</span><span class="n">val</span> <span class="o">=</span> <span class="n">val</span>
|
||||
<a id="__codelineno-25-7" name="__codelineno-25-7" href="#__codelineno-25-7"></a>
|
||||
<a id="__codelineno-25-8" name="__codelineno-25-8" href="#__codelineno-25-8"></a><span class="k">class</span> <span class="nc">ArrayHashMap</span><span class="p">:</span>
|
||||
<a id="__codelineno-25-9" name="__codelineno-25-9" href="#__codelineno-25-9"></a><span class="w"> </span><span class="sd">"""基于数组简易实现的哈希表"""</span>
|
||||
<a id="__codelineno-25-9" name="__codelineno-25-9" href="#__codelineno-25-9"></a><span class="w"> </span><span class="sd">"""基于数组实现的哈希表"""</span>
|
||||
<a id="__codelineno-25-10" name="__codelineno-25-10" href="#__codelineno-25-10"></a>
|
||||
<a id="__codelineno-25-11" name="__codelineno-25-11" href="#__codelineno-25-11"></a> <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<a id="__codelineno-25-12" name="__codelineno-25-12" href="#__codelineno-25-12"></a><span class="w"> </span><span class="sd">"""构造方法"""</span>
|
||||
@@ -3943,7 +3943,7 @@
|
||||
<a id="__codelineno-26-9" name="__codelineno-26-9" href="#__codelineno-26-9"></a><span class="w"> </span><span class="p">}</span>
|
||||
<a id="__codelineno-26-10" name="__codelineno-26-10" href="#__codelineno-26-10"></a><span class="p">};</span>
|
||||
<a id="__codelineno-26-11" name="__codelineno-26-11" href="#__codelineno-26-11"></a>
|
||||
<a id="__codelineno-26-12" name="__codelineno-26-12" href="#__codelineno-26-12"></a><span class="cm">/* 基于数组简易实现的哈希表 */</span>
|
||||
<a id="__codelineno-26-12" name="__codelineno-26-12" href="#__codelineno-26-12"></a><span class="cm">/* 基于数组实现的哈希表 */</span>
|
||||
<a id="__codelineno-26-13" name="__codelineno-26-13" href="#__codelineno-26-13"></a><span class="k">class</span><span class="w"> </span><span class="nc">ArrayHashMap</span><span class="w"> </span><span class="p">{</span>
|
||||
<a id="__codelineno-26-14" name="__codelineno-26-14" href="#__codelineno-26-14"></a><span class="w"> </span><span class="k">private</span><span class="o">:</span>
|
||||
<a id="__codelineno-26-15" name="__codelineno-26-15" href="#__codelineno-26-15"></a><span class="w"> </span><span class="n">vector</span><span class="o"><</span><span class="n">Pair</span><span class="w"> </span><span class="o">*></span><span class="w"> </span><span class="n">buckets</span><span class="p">;</span>
|
||||
@@ -4046,7 +4046,7 @@
|
||||
<a id="__codelineno-27-9" name="__codelineno-27-9" href="#__codelineno-27-9"></a><span class="w"> </span><span class="p">}</span>
|
||||
<a id="__codelineno-27-10" name="__codelineno-27-10" href="#__codelineno-27-10"></a><span class="p">}</span>
|
||||
<a id="__codelineno-27-11" name="__codelineno-27-11" href="#__codelineno-27-11"></a>
|
||||
<a id="__codelineno-27-12" name="__codelineno-27-12" href="#__codelineno-27-12"></a><span class="cm">/* 基于数组简易实现的哈希表 */</span>
|
||||
<a id="__codelineno-27-12" name="__codelineno-27-12" href="#__codelineno-27-12"></a><span class="cm">/* 基于数组实现的哈希表 */</span>
|
||||
<a id="__codelineno-27-13" name="__codelineno-27-13" href="#__codelineno-27-13"></a><span class="kd">class</span> <span class="nc">ArrayHashMap</span><span class="w"> </span><span class="p">{</span>
|
||||
<a id="__codelineno-27-14" name="__codelineno-27-14" href="#__codelineno-27-14"></a><span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="n">List</span><span class="o"><</span><span class="n">Pair</span><span class="o">></span><span class="w"> </span><span class="n">buckets</span><span class="p">;</span>
|
||||
<a id="__codelineno-27-15" name="__codelineno-27-15" href="#__codelineno-27-15"></a>
|
||||
@@ -4133,7 +4133,7 @@
|
||||
<a id="__codelineno-28-4" name="__codelineno-28-4" href="#__codelineno-28-4"></a><span class="w"> </span><span class="k">public</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="n">val</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">val</span><span class="p">;</span>
|
||||
<a id="__codelineno-28-5" name="__codelineno-28-5" href="#__codelineno-28-5"></a><span class="p">}</span>
|
||||
<a id="__codelineno-28-6" name="__codelineno-28-6" href="#__codelineno-28-6"></a>
|
||||
<a id="__codelineno-28-7" name="__codelineno-28-7" href="#__codelineno-28-7"></a><span class="cm">/* 基于数组简易实现的哈希表 */</span>
|
||||
<a id="__codelineno-28-7" name="__codelineno-28-7" href="#__codelineno-28-7"></a><span class="cm">/* 基于数组实现的哈希表 */</span>
|
||||
<a id="__codelineno-28-8" name="__codelineno-28-8" href="#__codelineno-28-8"></a><span class="k">class</span><span class="w"> </span><span class="nc">ArrayHashMap</span><span class="w"> </span><span class="p">{</span>
|
||||
<a id="__codelineno-28-9" name="__codelineno-28-9" href="#__codelineno-28-9"></a><span class="w"> </span><span class="n">List</span><span class="o"><</span><span class="n">Pair</span><span class="o">?></span><span class="w"> </span><span class="n">buckets</span><span class="p">;</span>
|
||||
<a id="__codelineno-28-10" name="__codelineno-28-10" href="#__codelineno-28-10"></a><span class="w"> </span><span class="k">public</span><span class="w"> </span><span class="nf">ArrayHashMap</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
|
||||
@@ -4218,7 +4218,7 @@
|
||||
<a id="__codelineno-29-4" name="__codelineno-29-4" href="#__codelineno-29-4"></a><span class="w"> </span><span class="nx">val</span><span class="w"> </span><span class="kt">string</span>
|
||||
<a id="__codelineno-29-5" name="__codelineno-29-5" href="#__codelineno-29-5"></a><span class="p">}</span>
|
||||
<a id="__codelineno-29-6" name="__codelineno-29-6" href="#__codelineno-29-6"></a>
|
||||
<a id="__codelineno-29-7" name="__codelineno-29-7" href="#__codelineno-29-7"></a><span class="cm">/* 基于数组简易实现的哈希表 */</span>
|
||||
<a id="__codelineno-29-7" name="__codelineno-29-7" href="#__codelineno-29-7"></a><span class="cm">/* 基于数组实现的哈希表 */</span>
|
||||
<a id="__codelineno-29-8" name="__codelineno-29-8" href="#__codelineno-29-8"></a><span class="kd">type</span><span class="w"> </span><span class="nx">arrayHashMap</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span>
|
||||
<a id="__codelineno-29-9" name="__codelineno-29-9" href="#__codelineno-29-9"></a><span class="w"> </span><span class="nx">buckets</span><span class="w"> </span><span class="p">[]</span><span class="o">*</span><span class="nx">pair</span>
|
||||
<a id="__codelineno-29-10" name="__codelineno-29-10" href="#__codelineno-29-10"></a><span class="p">}</span>
|
||||
@@ -4319,7 +4319,7 @@
|
||||
<a id="__codelineno-30-13" name="__codelineno-30-13" href="#__codelineno-30-13"></a> <span class="p">}</span>
|
||||
<a id="__codelineno-30-14" name="__codelineno-30-14" href="#__codelineno-30-14"></a><span class="p">}</span>
|
||||
<a id="__codelineno-30-15" name="__codelineno-30-15" href="#__codelineno-30-15"></a>
|
||||
<a id="__codelineno-30-16" name="__codelineno-30-16" href="#__codelineno-30-16"></a><span class="cm">/* 基于数组简易实现的哈希表 */</span>
|
||||
<a id="__codelineno-30-16" name="__codelineno-30-16" href="#__codelineno-30-16"></a><span class="cm">/* 基于数组实现的哈希表 */</span>
|
||||
<a id="__codelineno-30-17" name="__codelineno-30-17" href="#__codelineno-30-17"></a><span class="kd">class</span> <span class="nc">ArrayHashMap</span> <span class="p">{</span>
|
||||
<a id="__codelineno-30-18" name="__codelineno-30-18" href="#__codelineno-30-18"></a> <span class="kd">private</span> <span class="kd">var</span> <span class="nv">buckets</span><span class="p">:</span> <span class="p">[</span><span class="n">Pair</span><span class="p">?]</span> <span class="p">=</span> <span class="p">[]</span>
|
||||
<a id="__codelineno-30-19" name="__codelineno-30-19" href="#__codelineno-30-19"></a>
|
||||
@@ -4408,7 +4408,7 @@
|
||||
<a id="__codelineno-31-6" name="__codelineno-31-6" href="#__codelineno-31-6"></a><span class="w"> </span><span class="p">}</span>
|
||||
<a id="__codelineno-31-7" name="__codelineno-31-7" href="#__codelineno-31-7"></a><span class="p">}</span>
|
||||
<a id="__codelineno-31-8" name="__codelineno-31-8" href="#__codelineno-31-8"></a>
|
||||
<a id="__codelineno-31-9" name="__codelineno-31-9" href="#__codelineno-31-9"></a><span class="cm">/* 基于数组简易实现的哈希表 */</span>
|
||||
<a id="__codelineno-31-9" name="__codelineno-31-9" href="#__codelineno-31-9"></a><span class="cm">/* 基于数组实现的哈希表 */</span>
|
||||
<a id="__codelineno-31-10" name="__codelineno-31-10" href="#__codelineno-31-10"></a><span class="kd">class</span><span class="w"> </span><span class="nx">ArrayHashMap</span><span class="w"> </span><span class="p">{</span>
|
||||
<a id="__codelineno-31-11" name="__codelineno-31-11" href="#__codelineno-31-11"></a><span class="w"> </span><span class="err">#</span><span class="nx">buckets</span><span class="p">;</span>
|
||||
<a id="__codelineno-31-12" name="__codelineno-31-12" href="#__codelineno-31-12"></a><span class="w"> </span><span class="kr">constructor</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
|
||||
@@ -4497,7 +4497,7 @@
|
||||
<a id="__codelineno-32-9" name="__codelineno-32-9" href="#__codelineno-32-9"></a><span class="w"> </span><span class="p">}</span>
|
||||
<a id="__codelineno-32-10" name="__codelineno-32-10" href="#__codelineno-32-10"></a><span class="p">}</span>
|
||||
<a id="__codelineno-32-11" name="__codelineno-32-11" href="#__codelineno-32-11"></a>
|
||||
<a id="__codelineno-32-12" name="__codelineno-32-12" href="#__codelineno-32-12"></a><span class="cm">/* 基于数组简易实现的哈希表 */</span>
|
||||
<a id="__codelineno-32-12" name="__codelineno-32-12" href="#__codelineno-32-12"></a><span class="cm">/* 基于数组实现的哈希表 */</span>
|
||||
<a id="__codelineno-32-13" name="__codelineno-32-13" href="#__codelineno-32-13"></a><span class="kd">class</span><span class="w"> </span><span class="nx">ArrayHashMap</span><span class="w"> </span><span class="p">{</span>
|
||||
<a id="__codelineno-32-14" name="__codelineno-32-14" href="#__codelineno-32-14"></a><span class="w"> </span><span class="k">private</span><span class="w"> </span><span class="k">readonly</span><span class="w"> </span><span class="nx">buckets</span><span class="o">:</span><span class="w"> </span><span class="p">(</span><span class="nx">Pair</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="kc">null</span><span class="p">)[];</span>
|
||||
<a id="__codelineno-32-15" name="__codelineno-32-15" href="#__codelineno-32-15"></a>
|
||||
@@ -4583,7 +4583,7 @@
|
||||
<a id="__codelineno-33-5" name="__codelineno-33-5" href="#__codelineno-33-5"></a><span class="w"> </span><span class="n">Pair</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">key</span><span class="p">,</span><span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="n">val</span><span class="p">);</span>
|
||||
<a id="__codelineno-33-6" name="__codelineno-33-6" href="#__codelineno-33-6"></a><span class="p">}</span>
|
||||
<a id="__codelineno-33-7" name="__codelineno-33-7" href="#__codelineno-33-7"></a>
|
||||
<a id="__codelineno-33-8" name="__codelineno-33-8" href="#__codelineno-33-8"></a><span class="cm">/* 基于数组简易实现的哈希表 */</span>
|
||||
<a id="__codelineno-33-8" name="__codelineno-33-8" href="#__codelineno-33-8"></a><span class="cm">/* 基于数组实现的哈希表 */</span>
|
||||
<a id="__codelineno-33-9" name="__codelineno-33-9" href="#__codelineno-33-9"></a><span class="kd">class</span><span class="w"> </span><span class="nc">ArrayHashMap</span><span class="w"> </span><span class="p">{</span>
|
||||
<a id="__codelineno-33-10" name="__codelineno-33-10" href="#__codelineno-33-10"></a><span class="w"> </span><span class="kd">late</span><span class="w"> </span><span class="n">List</span><span class="o"><</span><span class="n">Pair</span><span class="o">?></span><span class="w"> </span><span class="n">_buckets</span><span class="p">;</span>
|
||||
<a id="__codelineno-33-11" name="__codelineno-33-11" href="#__codelineno-33-11"></a>
|
||||
@@ -4671,7 +4671,7 @@
|
||||
<a id="__codelineno-34-5" name="__codelineno-34-5" href="#__codelineno-34-5"></a><span class="w"> </span><span class="k">pub</span><span class="w"> </span><span class="n">val</span>: <span class="nb">String</span><span class="p">,</span>
|
||||
<a id="__codelineno-34-6" name="__codelineno-34-6" href="#__codelineno-34-6"></a><span class="p">}</span>
|
||||
<a id="__codelineno-34-7" name="__codelineno-34-7" href="#__codelineno-34-7"></a>
|
||||
<a id="__codelineno-34-8" name="__codelineno-34-8" href="#__codelineno-34-8"></a><span class="cm">/* 基于数组简易实现的哈希表 */</span>
|
||||
<a id="__codelineno-34-8" name="__codelineno-34-8" href="#__codelineno-34-8"></a><span class="cm">/* 基于数组实现的哈希表 */</span>
|
||||
<a id="__codelineno-34-9" name="__codelineno-34-9" href="#__codelineno-34-9"></a><span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="nc">ArrayHashMap</span><span class="w"> </span><span class="p">{</span>
|
||||
<a id="__codelineno-34-10" name="__codelineno-34-10" href="#__codelineno-34-10"></a><span class="w"> </span><span class="n">buckets</span>: <span class="nb">Vec</span><span class="o"><</span><span class="nb">Option</span><span class="o"><</span><span class="n">Pair</span><span class="o">>></span>
|
||||
<a id="__codelineno-34-11" name="__codelineno-34-11" href="#__codelineno-34-11"></a><span class="p">}</span>
|
||||
@@ -4740,7 +4740,7 @@
|
||||
<a id="__codelineno-35-4" name="__codelineno-35-4" href="#__codelineno-35-4"></a><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">val</span><span class="p">;</span>
|
||||
<a id="__codelineno-35-5" name="__codelineno-35-5" href="#__codelineno-35-5"></a><span class="p">}</span><span class="w"> </span><span class="n">Pair</span><span class="p">;</span>
|
||||
<a id="__codelineno-35-6" name="__codelineno-35-6" href="#__codelineno-35-6"></a>
|
||||
<a id="__codelineno-35-7" name="__codelineno-35-7" href="#__codelineno-35-7"></a><span class="cm">/* 基于数组简易实现的哈希表 */</span>
|
||||
<a id="__codelineno-35-7" name="__codelineno-35-7" href="#__codelineno-35-7"></a><span class="cm">/* 基于数组实现的哈希表 */</span>
|
||||
<a id="__codelineno-35-8" name="__codelineno-35-8" href="#__codelineno-35-8"></a><span class="k">typedef</span><span class="w"> </span><span class="k">struct</span><span class="w"> </span><span class="p">{</span>
|
||||
<a id="__codelineno-35-9" name="__codelineno-35-9" href="#__codelineno-35-9"></a><span class="w"> </span><span class="n">Pair</span><span class="w"> </span><span class="o">*</span><span class="n">buckets</span><span class="p">[</span><span class="n">HASHTABLE_CAPACITY</span><span class="p">];</span>
|
||||
<a id="__codelineno-35-10" name="__codelineno-35-10" href="#__codelineno-35-10"></a><span class="p">}</span><span class="w"> </span><span class="n">ArrayHashMap</span><span class="p">;</span>
|
||||
@@ -4876,7 +4876,7 @@
|
||||
<a id="__codelineno-36-11" name="__codelineno-36-11" href="#__codelineno-36-11"></a><span class="w"> </span><span class="p">}</span>
|
||||
<a id="__codelineno-36-12" name="__codelineno-36-12" href="#__codelineno-36-12"></a><span class="p">};</span>
|
||||
<a id="__codelineno-36-13" name="__codelineno-36-13" href="#__codelineno-36-13"></a>
|
||||
<a id="__codelineno-36-14" name="__codelineno-36-14" href="#__codelineno-36-14"></a><span class="c1">// 基于数组简易实现的哈希表</span>
|
||||
<a id="__codelineno-36-14" name="__codelineno-36-14" href="#__codelineno-36-14"></a><span class="c1">// 基于数组实现的哈希表</span>
|
||||
<a id="__codelineno-36-15" name="__codelineno-36-15" href="#__codelineno-36-15"></a><span class="k">fn</span><span class="w"> </span><span class="n">ArrayHashMap</span><span class="p">(</span><span class="kr">comptime</span><span class="w"> </span><span class="n">T</span><span class="o">:</span><span class="w"> </span><span class="kt">type</span><span class="p">)</span><span class="w"> </span><span class="kt">type</span><span class="w"> </span><span class="p">{</span>
|
||||
<a id="__codelineno-36-16" name="__codelineno-36-16" href="#__codelineno-36-16"></a><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="k">struct</span><span class="w"> </span><span class="p">{</span>
|
||||
<a id="__codelineno-36-17" name="__codelineno-36-17" href="#__codelineno-36-17"></a><span class="w"> </span><span class="n">bucket</span><span class="o">:</span><span class="w"> </span><span class="o">?</span><span class="n">std</span><span class="p">.</span><span class="n">ArrayList</span><span class="p">(</span><span class="o">?</span><span class="n">T</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span>
|
||||
@@ -4972,7 +4972,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<h2 id="613">6.1.3 哈希冲突与扩容<a class="headerlink" href="#613" title="Permanent link">¶</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-37-1" name="__codelineno-37-1" href="#__codelineno-37-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-37-2" name="__codelineno-37-2" href="#__codelineno-37-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>
|
||||
@@ -4986,8 +4986,8 @@
|
||||
<p><a class="glightbox" href="../hash_map.assets/hash_table_reshash.png" data-type="image" data-width="100%" data-height="auto" data-desc-position="bottom"><img alt="哈希表扩容" class="animation-figure" src="../hash_map.assets/hash_table_reshash.png" /></a></p>
|
||||
<p align="center"> 图 6-4 哈希表扩容 </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>
|
||||
|
||||
<!-- Source file information -->
|
||||
|
||||
|
||||
@@ -3326,8 +3326,8 @@
|
||||
</div>
|
||||
<div class="admonition abstract">
|
||||
<p class="admonition-title">Abstract</p>
|
||||
<p>在计算机世界中,哈希表如同一位智能的图书管理员。</p>
|
||||
<p>他知道如何计算索书号,从而可以快速找到目标书籍。</p>
|
||||
<p>在计算机世界中,哈希表如同一位聪慧的图书管理员。</p>
|
||||
<p>他知道如何计算索书号,从而可以快速找到目标图书。</p>
|
||||
</div>
|
||||
<h2 id="_1">本章内容<a class="headerlink" href="#_1" title="Permanent link">¶</a></h2>
|
||||
<ul>
|
||||
|
||||
@@ -3390,32 +3390,32 @@
|
||||
<li>两个不同的 <code>key</code> 可能在经过哈希函数后得到相同的数组索引,导致查询结果出错,这种现象被称为哈希冲突。</li>
|
||||
<li>哈希表容量越大,哈希冲突的概率就越低。因此可以通过扩容哈希表来缓解哈希冲突。与数组扩容类似,哈希表扩容操作的开销很大。</li>
|
||||
<li>负载因子定义为哈希表中元素数量除以桶数量,反映了哈希冲突的严重程度,常用作触发哈希表扩容的条件。</li>
|
||||
<li>链式地址通过将单个元素转化为链表,将所有冲突元素存储在同一个链表中。然而,链表过长会降低查询效率,可以进一步将链表转换为红黑树来提高效率。</li>
|
||||
<li>链式地址通过将单个元素转化为链表,将所有冲突元素存储在同一个链表中。然而,链表过长会降低查询效率,可以通过进一步将链表转换为红黑树来提高效率。</li>
|
||||
<li>开放寻址通过多次探测来处理哈希冲突。线性探测使用固定步长,缺点是不能删除元素,且容易产生聚集。多次哈希使用多个哈希函数进行探测,相较线性探测更不易产生聚集,但多个哈希函数增加了计算量。</li>
|
||||
<li>不同编程语言采取了不同的哈希表实现。例如,Java 的 <code>HashMap</code> 使用链式地址,而 Python 的 <code>Dict</code> 采用开放寻址。</li>
|
||||
<li>在哈希表中,我们希望哈希算法具有确定性、高效率和均匀分布的特点。在密码学中,哈希算法还应该具备抗碰撞性和雪崩效应。</li>
|
||||
<li>哈希算法通常采用大质数作为模数,以最大化地保证哈希值的均匀分布,减少哈希冲突。</li>
|
||||
<li>常见的哈希算法包括 MD5、SHA-1、SHA-2 和 SHA3 等。MD5 常用于校验文件完整性,SHA-2 常用于安全应用与协议。</li>
|
||||
<li>哈希算法通常采用大质数作为模数,以最大化地保证哈希值均匀分布,减少哈希冲突。</li>
|
||||
<li>常见的哈希算法包括 MD5、SHA-1、SHA-2 和 SHA-3 等。MD5 常用于校验文件完整性,SHA-2 常用于安全应用与协议。</li>
|
||||
<li>编程语言通常会为数据类型提供内置哈希算法,用于计算哈希表中的桶索引。通常情况下,只有不可变对象是可哈希的。</li>
|
||||
</ul>
|
||||
<h3 id="2-q-a">2. Q & A<a class="headerlink" href="#2-q-a" title="Permanent link">¶</a></h3>
|
||||
<div class="admonition question">
|
||||
<p class="admonition-title">哈希表的时间复杂度为什么不是 <span class="arithmatex">\(O(n)\)</span> ?</p>
|
||||
<p>当哈希冲突比较严重时,哈希表的时间复杂度会退化至 <span class="arithmatex">\(O(n)\)</span> 。当哈希函数设计的比较好、容量设置比较合理、冲突比较平均时,时间复杂度是 <span class="arithmatex">\(O(1)\)</span> 。我们使用编程语言内置的哈希表时,通常认为时间复杂度是 <span class="arithmatex">\(O(1)\)</span> 。</p>
|
||||
<p>当哈希冲突比较严重时,哈希表的时间复杂度会退化至 <span class="arithmatex">\(O(n)\)</span> 。当哈希函数设计得比较好、容量设置比较合理、冲突比较平均时,时间复杂度是 <span class="arithmatex">\(O(1)\)</span> 。我们使用编程语言内置的哈希表时,通常认为时间复杂度是 <span class="arithmatex">\(O(1)\)</span> 。</p>
|
||||
</div>
|
||||
<div class="admonition question">
|
||||
<p class="admonition-title">为什么不使用哈希函数 <span class="arithmatex">\(f(x) = x\)</span> 呢?这样就不会有冲突了</p>
|
||||
<p>在 <span class="arithmatex">\(f(x) = x\)</span> 哈希函数下,每个元素对应唯一的桶索引,这与数组等价。然而,输入空间通常远大于输出空间(数组长度),因此哈希函数的最后一步往往是对数组长度取模。换句话说,哈希表的目标是将一个较大的状态空间映射到一个较小的空间,并提供 <span class="arithmatex">\(O(1)\)</span> 的查询效率。</p>
|
||||
</div>
|
||||
<div class="admonition question">
|
||||
<p class="admonition-title">哈希表底层实现是数组、链表、二叉树,但为什么效率可以比他们更高呢?</p>
|
||||
<p>首先,哈希表的时间效率变高,但空间效率变低了。哈希表有相当一部分的内存是未使用的,</p>
|
||||
<p class="admonition-title">哈希表底层实现是数组、链表、二叉树,但为什么效率可以比它们更高呢?</p>
|
||||
<p>首先,哈希表的时间效率变高,但空间效率变低了。哈希表有相当一部分内存未使用。</p>
|
||||
<p>其次,只是在特定使用场景下时间效率变高了。如果一个功能能够在相同的时间复杂度下使用数组或链表实现,那么通常比哈希表更快。这是因为哈希函数计算需要开销,时间复杂度的常数项更大。</p>
|
||||
<p>最后,哈希表的时间复杂度可能发生劣化。例如在链式地址中,我们采取在链表或红黑树中执行查找操作,仍然有退化至 <span class="arithmatex">\(O(n)\)</span> 时间的风险。</p>
|
||||
</div>
|
||||
<div class="admonition question">
|
||||
<p class="admonition-title">多次哈希有不能直接删除元素的缺陷吗?对于标记已删除的空间,这个空间还能再次使用吗?</p>
|
||||
<p>多次哈希是开放寻址的一种,开放寻址法都有不能直接删除元素的缺陷,需要通过标记删除。被标记为已删除的空间是可以再次被使用的。当将新元素插入哈希表,并且通过哈希函数找到了被标记为已删除的位置时,该位置可以被新的元素使用。这样做既能保持哈希表的探测序列不变,又能保证哈希表的空间使用率。</p>
|
||||
<p class="admonition-title">多次哈希有不能直接删除元素的缺陷吗?标记为已删除的空间还能再次使用吗?</p>
|
||||
<p>多次哈希是开放寻址的一种,开放寻址法都有不能直接删除元素的缺陷,需要通过标记删除。标记为已删除的空间可以再次使用。当将新元素插入哈希表,并且通过哈希函数找到标记为已删除的位置时,该位置可以被新元素使用。这样做既能保持哈希表的探测序列不变,又能保证哈希表的空间使用率。</p>
|
||||
</div>
|
||||
<div class="admonition question">
|
||||
<p class="admonition-title">为什么在线性探测中,查找元素的时候会出现哈希冲突呢?</p>
|
||||
@@ -3423,7 +3423,7 @@
|
||||
</div>
|
||||
<div class="admonition question">
|
||||
<p class="admonition-title">为什么哈希表扩容能够缓解哈希冲突?</p>
|
||||
<p>哈希函数的最后一步往往是对数组长度 <span class="arithmatex">\(n\)</span> 取余,让输出值落入在数组索引范围;在扩容后,数组长度 <span class="arithmatex">\(n\)</span> 发生变化,而 <code>key</code> 对应的索引也可能发生变化。原先落在同一个桶的多个 <code>key</code> ,在扩容后可能会被分配到多个桶中,从而实现哈希冲突的缓解。</p>
|
||||
<p>哈希函数的最后一步往往是对数组长度 <span class="arithmatex">\(n\)</span> 取余,让输出值落在数组索引范围内;在扩容后,数组长度 <span class="arithmatex">\(n\)</span> 发生变化,而 <code>key</code> 对应的索引也可能发生变化。原先落在同一个桶的多个 <code>key</code> ,在扩容后可能会被分配到多个桶中,从而实现哈希冲突的缓解。</p>
|
||||
</div>
|
||||
|
||||
<!-- Source file information -->
|
||||
|
||||
Reference in New Issue
Block a user