This commit is contained in:
krahets
2023-04-09 02:53:32 +08:00
parent c6da3ca087
commit fc4021ea99
14 changed files with 202 additions and 202 deletions

View File

@@ -1768,20 +1768,20 @@
<h1 id="61">6.1. &nbsp; 哈希表<a class="headerlink" href="#61" title="Permanent link">&para;</a></h1>
<p>哈希表通过建立「键 key」「值 value」之间的映射实现高效的元素查。具体地,输入一个 key ,在哈希表中查询并获取 value ,时间复杂度为 <span class="arithmatex">\(O(1)\)</span></p>
<p>例如,给定一个包含 <span class="arithmatex">\(n\)</span> 个学生的数据库,每个学生有“姓名 <code>name</code> ”和“学号 <code>id</code> ”两项数据希望实现一个查询功能<strong>输入一个学号,返回对应的姓名</strong>,则可以使用哈希表实现。</p>
<p>哈希表通过建立「键 key」「值 value」之间的映射实现高效的元素查。具体而言,我们向哈希表输入一个 key则可以在 <span class="arithmatex">\(O(1)\)</span> 时间内获取对应的 value</p>
<p>一个包含 <span class="arithmatex">\(n\)</span> 个学生的数据库为例,每个学生有“姓名 <code>name</code>”和“学号 <code>id</code>”两项数据。假如我们希望实现查询功能,例如“输入一个学号,返回对应的姓名”,则可以用哈希表实现。</p>
<p><img alt="哈希表的抽象表示" src="../hash_map.assets/hash_map.png" /></p>
<p align="center"> Fig. 哈希表的抽象表示 </p>
<h2 id="611">6.1.1. &nbsp; 哈希表效率<a class="headerlink" href="#611" title="Permanent link">&para;</a></h2>
<p>哈希表外,还可以使用以下数据结构来实现上述查询功能:</p>
<p>除哈希表外,还可以使用以下数据结构来实现上述查询功能:</p>
<ol>
<li><strong>无序数组</strong>:每个元素为 <code>[学号, 姓名]</code> </li>
<li><strong>有序数组</strong>:将 <code>1.</code> 中的数组按照学号从小到大排序;</li>
<li><strong>链表</strong>:每个结点的值为 <code>[学号, 姓名]</code> </li>
<li><strong>二叉搜索树</strong>:每个结点的值为 <code>[学号, 姓名]</code> ,根据学号大小来构建树;</li>
</ol>
<p>使用上述方法,各项操作的时间复杂度如下表所示(在此不做赘述,详解可见 <a href="https://www.hello-algo.com/chapter_tree/binary_search_tree/">二叉搜索树章节</a>)。无论是查找元素还是增删元素,哈希表的时间复杂度都是 <span class="arithmatex">\(O(1)\)</span> ,全面胜出!</p>
<p>各项操作的时间复杂度如下表所示(详解可见<a href="https://www.hello-algo.com/chapter_tree/binary_search_tree/">二叉搜索树章节</a>)。无论是查找元素还是增删元素,哈希表的时间复杂度都是 <span class="arithmatex">\(O(1)\)</span>,全面胜出!</p>
<div class="center-table">
<table>
<thead>
@@ -2149,15 +2149,15 @@
</div>
</div>
<h2 id="613">6.1.3. &nbsp; 哈希函数<a class="headerlink" href="#613" title="Permanent link">&para;</a></h2>
<p>哈希表的底层实现数组,并且可能包含链表、二叉树(红黑树)等数据结构,以提查询性能(下节讨论)。</p>
<p>首先考虑最简单的情况,<strong>仅用一个数组来实现哈希表</strong>根据习惯,我们将数组中的每个空位称为「桶 Bucket」用于存储键值对。</p>
<p>我们将键值对 key, value 装成一个类 <code>Entry</code> ,并将所有 <code>Entry</code> 放入数组中,那么每个 <code>Entry</code> 在数组中都有唯一的索引。为了建立 key 和索引之间的映射关系,我们需要使用「哈希函数 Hash Function」。</p>
<p>设哈希表的数组为 <code>buckets</code> ,哈希函数为 <code>f(x)</code> ,那么查询操作的步骤</p>
<p>哈希表的底层实现数组,同时可能包含链表、二叉树(红黑树)等数据结构,以提查询性能(将在下节讨论)。</p>
<p>首先考虑最简单的情况,<strong>使用一个数组来实现哈希表</strong>通常,我们将数组中的每个空位称为「桶 Bucket」用于存储键值对。</p>
<p>我们将键值对 key, value 装成一个类 <code>Entry</code> ,并将所有 <code>Entry</code> 放入数组中。这样,数组中的每个 <code>Entry</code>有唯一的索引。为了建立 key 和索引之间的映射关系,我们需要使用「哈希函数 Hash Function」。</p>
<p>设哈希表的数组为 <code>buckets</code> ,哈希函数为 <code>f(x)</code> ,那么查询操作的步骤如下</p>
<ol>
<li>输入 <code>key</code> ,通过哈希函数计算出索引 <code>index</code> ,即 <code>index = f(key)</code> </li>
<li>通过索引在数组中访问到键值对 <code>entry</code> ,即 <code>entry = buckets[index]</code> 并在 <code>entry</code> 中获取 <code>value</code> 即可</li>
<li>通过索引在数组中访问到键值对 <code>entry</code> ,即 <code>entry = buckets[index]</code> 然后从 <code>entry</code> 中获取对应的 <code>value</code> </li>
</ol>
<p>上述学生数据 <code>key 学号 -&gt; value 姓名</code> 为例,我们可以将「哈希函数」设计为</p>
<p>以学生数据 <code>key 学号 -&gt; value 姓名</code> 为例,我们可以设计如下哈希函数:</p>
<div class="arithmatex">\[
f(x) = x \% 100
\]</div>
@@ -3003,19 +3003,19 @@ f(x) = x \% 100
</div>
</div>
<h2 id="614">6.1.4. &nbsp; 哈希冲突<a class="headerlink" href="#614" title="Permanent link">&para;</a></h2>
<p>细心的同学可能会发现<strong>哈希函数 <span class="arithmatex">\(f(x) = x \% 100\)</span> 会在某些情况下失效</strong>。具体,当输入的 key 后两位相同时,哈希函数的计算结果也相同,指向同一个 value 。例如,分别查询两个学号 <span class="arithmatex">\(12836\)</span><span class="arithmatex">\(20336\)</span> ,则有</p>
<p>细心的你可能已经注意到<strong>在某些情况下,哈希函数 <span class="arithmatex">\(f(x) = x % 100\)</span> 可能无法正常工作</strong>。具体来说,当输入的 key 后两位相同时,哈希函数的计算结果也相同,从而指向同一个 value 。例如,查询学号 <span class="arithmatex">\(12836\)</span><span class="arithmatex">\(20336\)</span> 的两个学生时,我们得到:</p>
<div class="arithmatex">\[
f(12836) = f(20336) = 36
\]</div>
<p>两个学号指向了同一个姓名,这明显是不对的,我们这种现象称为「哈希冲突 Hash Collision」。如何避免哈希冲突的问题将被留在下章讨论</p>
<p>两个学号指向了同一个姓名,这显然是错误的。我们这种情况称为「哈希冲突 Hash Collision」。在后续章节中,我们将讨论如何解决哈希冲突的问题</p>
<p><img alt="哈希冲突示例" src="../hash_map.assets/hash_collision.png" /></p>
<p align="center"> Fig. 哈希冲突示例 </p>
<p>综上所述,一个优秀的哈希函数」应该具备以下特性:</p>
<p>综上所述,一个优秀的哈希函数具备以下特性:</p>
<ul>
<li>量少地发生哈希冲突;</li>
<li>时间复杂度 <span class="arithmatex">\(O(1)\)</span> ,计算尽可能高效</li>
<li>空间使用率,即“键值对占用空间 / 哈希表总占用空间”尽可能大</li>
<li>可能减少哈希冲突的发生</li>
<li>查询效率高且稳定,能够在绝大多数情况下达到 <span class="arithmatex">\(O(1)\)</span> 时间复杂度</li>
<li>较高的空间用率,即使“键值对占用空间 / 哈希表总占用空间”比例最大化</li>
</ul>