mirror of
https://github.com/krahets/hello-algo.git
synced 2026-04-26 11:32:31 +08:00
deploy
This commit is contained in:
@@ -1768,20 +1768,20 @@
|
||||
|
||||
|
||||
<h1 id="61">6.1. 哈希表<a class="headerlink" href="#61" title="Permanent link">¶</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. 哈希表效率<a class="headerlink" href="#611" title="Permanent link">¶</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. 哈希函数<a class="headerlink" href="#613" title="Permanent link">¶</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 学号 -> value 姓名</code> 为例,我们可以将「哈希函数」设计为</p>
|
||||
<p>以学生数据 <code>key 学号 -> 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. 哈希冲突<a class="headerlink" href="#614" title="Permanent link">¶</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>
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user