This commit is contained in:
krahets
2023-02-26 22:45:57 +08:00
parent c20bdb15ac
commit ccfe99d31c
56 changed files with 7768 additions and 85 deletions
+51 -3
View File
@@ -364,6 +364,8 @@
<label class="md-nav__link" for="__nav_2" id="__nav_2_label" tabindex="0">
@@ -406,6 +408,20 @@
<li class="md-nav__item">
<a href="../../chapter_introduction/summary/" class="md-nav__link">
1.3. &nbsp; 小结
</a>
</li>
</ul>
</nav>
</li>
@@ -1130,6 +1146,8 @@
<label class="md-nav__link" for="__nav_9" id="__nav_9_label" tabindex="0">
@@ -1172,6 +1190,20 @@
<li class="md-nav__item">
<a href="../../chapter_heap/summary/" class="md-nav__link">
8.3. &nbsp; 小结
</a>
</li>
</ul>
</nav>
</li>
@@ -1203,6 +1235,8 @@
<label class="md-nav__link" for="__nav_10" id="__nav_10_label" tabindex="0">
@@ -1259,6 +1293,20 @@
<li class="md-nav__item">
<a href="../../chapter_graph/summary/" class="md-nav__link">
9.4. &nbsp; 小结
</a>
</li>
</ul>
</nav>
</li>
@@ -1644,8 +1692,8 @@
<h1 id="62">6.2. &nbsp; 哈希冲突<a class="headerlink" href="#62" title="Permanent link">&para;</a></h1>
<p>理想情况下,哈希函数应该为每个输入产生唯一的输出,使得 key 和 value 一一对应。而实际上,往往存在向哈希函数输入不同的 key 而产生相同输出的情况,这种情况被称为「哈希冲突 Hash Collision」。哈希冲突会导致查询结果错误,从而严重影响哈希表的可用性。</p>
<p>那么,为什么会出现哈希冲突呢?本质上看,<strong>由于哈希函数的输入空间往往远大于输出空间</strong>,因此不可避免地会出现多个输入产生相同输出的情况,即为哈希冲突。比如,输入空间是全体整数,输出空间是一个固定大小的桶(数组)的索引范围,那么必定会有多个整数同时映射到一个桶索引。</p>
<p>为了缓解哈希冲突,一方面,我们可以通过哈希表扩容来减小冲突概率。极端情况下,当输入空间和输出空间大小相等时,哈希表就等价于数组了,可谓“大力出奇迹”。</p>
<p>另一方面,<strong>考虑通过优化数据结构以缓解哈希冲突</strong>,常见的方法有「链式地址」和「开放寻址」。</p>
<p>为了缓解哈希冲突,一方面,<strong>我们可以通过哈希表扩容来减小冲突概率</strong>。极端情况下,当输入空间和输出空间大小相等时,哈希表就等价于数组了,可谓“大力出奇迹”。</p>
<p>另一方面,<strong>考虑通过优化哈希表的表示方式以缓解哈希冲突</strong>,常见的方法有「链式地址」和「开放寻址」。</p>
<h2 id="621">6.2.1. &nbsp; 哈希表扩容<a class="headerlink" href="#621" title="Permanent link">&para;</a></h2>
<p>「负载因子 Load Factor」定义为 <strong>哈希表中元素数量除以桶槽数量(即数组大小)</strong>,代表哈希冲突的严重程度。</p>
<p><strong>负载因子常用作哈希表扩容的触发条件</strong>。比如在 Java 中,当负载因子 <span class="arithmatex">\(&gt; 0.75\)</span> 时则触发扩容,将 HashMap 大小扩充至原先的 <span class="arithmatex">\(2\)</span> 倍。</p>
@@ -1666,7 +1714,7 @@
<li><strong>占用空间变大</strong>,因为链表或二叉树包含结点指针,相比于数组更加耗费内存空间;</li>
<li><strong>查询效率降低</strong>,因为需要线性遍历链表来查找对应元素;</li>
</ul>
<p>为了缓解时间效率问题<strong>可以把「链表」转化为「AVL 树」或「红黑树」</strong>,将查询操作的时间复杂度优化至 <span class="arithmatex">\(O(\log n)\)</span></p>
<p>为了提升操作效率<strong>可以把「链表」转化为「AVL 树」或「红黑树」</strong>,将查询操作的时间复杂度优化至 <span class="arithmatex">\(O(\log n)\)</span></p>
<h2 id="623">6.2.3. &nbsp; 开放寻址<a class="headerlink" href="#623" title="Permanent link">&para;</a></h2>
<p>「开放寻址」不引入额外数据结构,而是通过“多次探测”来解决哈希冲突。根据探测方法的不同,主要分为 <strong>线性探测、平方探测、多次哈希</strong></p>
<h3 id="_1">线性探测<a class="headerlink" href="#_1" title="Permanent link">&para;</a></h3>
+48
View File
@@ -364,6 +364,8 @@
<label class="md-nav__link" for="__nav_2" id="__nav_2_label" tabindex="0">
@@ -406,6 +408,20 @@
<li class="md-nav__item">
<a href="../../chapter_introduction/summary/" class="md-nav__link">
1.3. &nbsp; 小结
</a>
</li>
</ul>
</nav>
</li>
@@ -1117,6 +1133,8 @@
<label class="md-nav__link" for="__nav_9" id="__nav_9_label" tabindex="0">
@@ -1159,6 +1177,20 @@
<li class="md-nav__item">
<a href="../../chapter_heap/summary/" class="md-nav__link">
8.3. &nbsp; 小结
</a>
</li>
</ul>
</nav>
</li>
@@ -1190,6 +1222,8 @@
<label class="md-nav__link" for="__nav_10" id="__nav_10_label" tabindex="0">
@@ -1246,6 +1280,20 @@
<li class="md-nav__item">
<a href="../../chapter_graph/summary/" class="md-nav__link">
9.4. &nbsp; 小结
</a>
</li>
</ul>
</nav>
</li>
+59
View File
@@ -364,6 +364,8 @@
<label class="md-nav__link" for="__nav_2" id="__nav_2_label" tabindex="0">
@@ -406,6 +408,20 @@
<li class="md-nav__item">
<a href="../../chapter_introduction/summary/" class="md-nav__link">
1.3. &nbsp; 小结
</a>
</li>
</ul>
</nav>
</li>
@@ -1065,6 +1081,8 @@
<label class="md-nav__link" for="__nav_9" id="__nav_9_label" tabindex="0">
@@ -1107,6 +1125,20 @@
<li class="md-nav__item">
<a href="../../chapter_heap/summary/" class="md-nav__link">
8.3. &nbsp; 小结
</a>
</li>
</ul>
</nav>
</li>
@@ -1138,6 +1170,8 @@
<label class="md-nav__link" for="__nav_10" id="__nav_10_label" tabindex="0">
@@ -1194,6 +1228,20 @@
<li class="md-nav__item">
<a href="../../chapter_graph/summary/" class="md-nav__link">
9.4. &nbsp; 小结
</a>
</li>
</ul>
</nav>
</li>
@@ -1528,6 +1576,17 @@
<h1 id="63">6.3. &nbsp; 小结<a class="headerlink" href="#63" title="Permanent link">&para;</a></h1>
<ul>
<li>向哈希表中输入一个键 key ,查询到值 value 的时间复杂度为 <span class="arithmatex">\(O(1)\)</span> ,非常高效。</li>
<li>哈希表的常用操作包括查询、添加与删除键值对、遍历键值对等。</li>
<li>哈希函数将 key 映射到桶(数组)索引,从而访问到对应的值 value 。</li>
<li>两个不同的 key 经过哈希函数可能得到相同的桶索引,进而发生哈希冲突,导致查询错误。</li>
<li>缓解哈希冲突的途径有两种:哈希表扩容、优化哈希表的表示方式。</li>
<li>负载因子定义为哈希表中元素数量除以桶槽数量,体现哈希冲突的严重程度,常用作哈希表扩容的触发条件。与数组扩容的原理类似,哈希表扩容操作开销也很大。</li>
<li>链式地址考虑将单个元素转化成一个链表,将所有冲突元素都存储在一个链表中,从而解决哈希冲突。而为了提升查询效率,可以把链表转化为 AVL 树或红黑树,</li>
<li>开放寻址通过多次探测来解决哈希冲突。线性探测使用固定步长,缺点是不能删除元素且容易产生聚集。多次哈希使用多个哈希函数进行探测,相对线性探测不容易产生聚集,代价是多个哈希函数增加了计算量。</li>
<li>在工业界中,Java 的 HashMap 采用链式地址、Python 的 Dict 采用开放寻址。</li>
</ul>