Skip to content

Commit b106e08

Browse files
committed
Deployed 3e74fbf with MkDocs version: 0.17.3
1 parent f8a503e commit b106e08

File tree

8 files changed

+171
-26
lines changed

8 files changed

+171
-26
lines changed

7_哈希表/hashtable/index.html

Lines changed: 123 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,25 @@
9595
<li class="toctree-l2"><a href="#_1">哈希表</a></li>
9696

9797

98-
<li class="toctree-l2"><a href="#o1">如何在 O(1) 时间内查找</a></li>
98+
<li class="toctree-l2"><a href="#_2">哈希表的工作过程</a></li>
99+
100+
101+
<li class="toctree-l2"><a href="#collision">哈希冲突 (collision)</a></li>
102+
103+
104+
<li class="toctree-l2"><a href="#_3">哈希函数</a></li>
105+
106+
107+
<li class="toctree-l2"><a href="#load-factor">装载因子(load factor)</a></li>
108+
109+
110+
<li class="toctree-l2"><a href="#rehashing">重哈希(Rehashing)</a></li>
111+
112+
113+
<li class="toctree-l2"><a href="#hashtalbe-adt">HashTalbe ADT</a></li>
114+
115+
116+
<li class="toctree-l2"><a href="#_4">延伸阅读</a></li>
99117

100118

101119
</ul>
@@ -135,15 +153,112 @@
135153

136154
<h1 id="_1">哈希表</h1>
137155
<p>不知道你有没有好奇过为什么 Python 里的 dict 和 set 查找速度这么快呢,用了什么黑魔法吗?
138-
经常听别人说哈希表,究竟什么是哈希表呢?这一章我们来介绍哈希表,后续章节我们会看到 Python 中的字典和集合是如何实现的。</p>
139-
<h1 id="o1">如何在 O(1) 时间内查找</h1>
156+
经常听别人说哈希表(也叫做散列表),究竟什么是哈希表呢?这一章我们来介绍哈希表,后续章节我们会看到 Python 中的字典和集合是如何实现的。</p>
157+
<h1 id="_2">哈希表的工作过程</h1>
140158
<p>前面我们已经讲到了数组和链表,数组能通过下标 O(1) 访问,但是删除一个中间元素却要移动其他元素,时间 O(n)。
141-
循环双端链表倒是可以在知道一个节点的情况下迅速删除它,但是吧查找又成了 O(n)。
142-
难道就没有一种方法可以快速定位和删除元素吗?似乎想要快速找到一个元素除了知道下标之外别无他法,于是乎聪明的计算机科学家又想到了一种方法。
143-
能不能给每个元素一种『逻辑下标』,然后直接找到它呢,哈希表就是这种实现。它通过一个函数来计算一个元素应该放在数组哪个位置,当然对于一个
144-
特定的元素,哈希函数每次计算的下标必须要一样才可以:</p>
145-
<pre><code>hash(element) = index
159+
循环双端链表倒是可以在知道一个节点的情况下迅速删除它,但是吧查找又成了 O(n)。</p>
160+
<p>难道就没有一种方法可以快速定位和删除元素吗?似乎想要快速找到一个元素除了知道下标之外别无他法,于是乎聪明的计算机科学家又想到了一种方法。
161+
能不能给每个元素一种『逻辑下标』,然后直接找到它呢,哈希表就是这种实现。它通过一个哈希函数来计算一个元素应该放在数组哪个位置,当然对于一个
162+
特定的元素,哈希函数每次计算的下标必须要一样才可以,而且范围不能超过给定的数组长度。</p>
163+
<p>我们还是以书中的例子说明,假如我们有一个数组 T,包含 M=13 个元素,我们可以定义一个简单的哈希函数 h</p>
164+
<pre><code>h(key) = key % M
165+
</code></pre>
166+
167+
<p>这里取模运算使得 h(key) 的结果不会超过数组的长度下标。我们来分别插入以下元素:</p>
168+
<p>765, 431, 96, 142, 579, 226, 903, 388</p>
169+
<p>先来计算下它们应用哈希函数后的结果:</p>
170+
<pre><code>M = 13
171+
h(765) = 765 % M = 11
172+
h(431) = 431 % M = 2
173+
h(96) = 96 % M = 5
174+
h(142) = 142 % M = 12
175+
h(579) = 579 % M = 7
176+
h(226) = 226 % M = 5
177+
h(903) = 903 % M = 6
178+
h(388) = 388 % M = 11
179+
</code></pre>
180+
181+
<p>下边我画个图演示整个插入过程:</p>
182+
<p><img alt="" src="../insert_hash.png" /></p>
183+
<h1 id="collision">哈希冲突 (collision)</h1>
184+
<p>这里到插入 226 这个元素的时候,不幸地发现 h(226) = h(96) = 5,不同的 key 通过我们的哈希函数计算后得到的下标一样,
185+
这种情况成为哈希冲突。怎么办呢?聪明的计算机科学家又想到了办法,其实一种直观的想法是如果冲突了我能不能让数组中
186+
对应的槽变成一个链式结构呢?这就是其中一种解决方法,叫做 <strong>链接法(chaining)</strong>。如果我们用链接法来处理冲突,后边的插入是这样的:</p>
187+
<p><img alt="" src="../insert_hash_chaining.png" /></p>
188+
<p>这样就用链表解决了冲突问题,但是如果哈希函数选不好的话,可能就导致冲突太多一个链变得太长,这样查找就不再是 O(1) 的了。
189+
还有一种叫做开放寻址法(open addressing),它的基本思想是当一个槽被占用的时候,采用一种方式来寻找下一个可用的槽。
190+
(这里槽指的是数组中的一个位置),根据找下一个槽的方式不同,分为:</p>
191+
<ul>
192+
<li>线性探查(linear probing): 当一个槽被占用,找下一个可用的槽。 <script type="math/tex"> h(k, i) = (h^\prime(k) + i) \% m, i = 0,1,...,m-1 </script>
193+
</li>
194+
<li>二次探查(quadratic probing): 当一个槽被占用,以二次方作为偏移量。 <script type="math/tex"> h(k, i) = (h^\prime(k) + c_1 + c_2i^2) \% m , i=0,1,...,m-1 </script>
195+
</li>
196+
<li>双重散列(double hashing): 重新计算 hash 结果。 <script type="math/tex"> h(k,i) = (h_1(k) + ih_2(k)) \% m </script>
197+
</li>
198+
</ul>
199+
<p>cpython 使用的是二次探查,这里我们也使用二次探查, 我们选一个简单的二次探查函数 <script type="math/tex"> h(k, i) = (home + i^2) \% m </script>,它的意思是如果
200+
遇到了冲突,我们就在原始计算的位置不断加上 i 的平方。我写了段代码来模拟整个计算下标的过程:</p>
201+
<pre><code class="py">inserted_index_set = set()
202+
M = 13
203+
204+
def h(key, M=13):
205+
return key % M
206+
207+
to_insert = [765, 431, 96, 142, 579, 226, 903, 388]
208+
for number in to_insert:
209+
index = h(number)
210+
first_index = index
211+
i = 1
212+
while index in inserted_index_set: # 如果计算发现已经占用,继续计算得到下一个可用槽的位置
213+
print('\th({number}) = {number} % M = {index} collision'.format(number=number, index=index))
214+
index = (first_index + i*i) % M
215+
i += 1
216+
else:
217+
print('h({number}) = {number} % M = {index}'.format(number=number, index=index))
218+
inserted_index_set.add(index)
146219
</code></pre>
220+
221+
<p>这段代码输出的结果如下:</p>
222+
<pre><code>h(765) = 765 % M = 11
223+
h(431) = 431 % M = 2
224+
h(96) = 96 % M = 5
225+
h(142) = 142 % M = 12
226+
h(579) = 579 % M = 7
227+
h(226) = 226 % M = 5 collision
228+
h(226) = 226 % M = 6
229+
h(903) = 903 % M = 6 collision
230+
h(903) = 903 % M = 7 collision
231+
h(903) = 903 % M = 10
232+
h(388) = 388 % M = 11 collision
233+
h(388) = 388 % M = 12 collision
234+
h(388) = 388 % M = 2 collision
235+
h(388) = 388 % M = 7 collision
236+
h(388) = 388 % M = 1
237+
</code></pre>
238+
239+
<p>遇到冲突之后会重新计算,每个待插入元素最终的下标就是:</p>
240+
<p><img alt="" src="../quadratic_hash.png" /></p>
241+
<p><img alt="" src="../quadratic_result.png" /></p>
242+
<h1 id="_3">哈希函数</h1>
243+
<p>到这里你应该明白哈希表插入的工作原理了,不过有个重要的问题之前没提到,就是 hash 函数怎么选?
244+
当然是散列得到的冲突越来越小就好啦,也就是说每个 key 都能尽量被等可能地散列到 m 个槽中的任何一个,并且与其他 key 被散列到哪个槽位无关。
245+
如果你感兴趣,可以阅读后边提到的一些参考资料。</p>
246+
<h1 id="load-factor">装载因子(load factor)</h1>
247+
<p>如果继续往我们的哈希表里塞东西会发生什么?空间不够用。这里我们定义一个负载因子的概念(load factor),其实很简单,就是已经使用的槽数比哈希表大小。
248+
比如我们上边的例子插入了 8 个元素,哈希表总大小是 13, 它的 load factor 就是 <script type="math/tex"> 8/13 \approx 0.62 </script>。当我们继续往哈希表插入数据的时候,很快就不够用了。
249+
通常当负载因子开始超过 0.8 的时候,就要新开辟空间了并且重新进行散列了。</p>
250+
<h1 id="rehashing">重哈希(Rehashing)</h1>
251+
<p>当负载因子超过 0.8 的时候,需要进行 rehashing 操作了。步骤就是重新开辟一块新的空间,开多大呢?感兴趣的话可以看下 cpython 的 dictobject.c 文件然后搜索
252+
GROWTH_RATE 这个关键字,你会发现不同版本的 cpython 使用了不同的策略。python3.3 的策略是扩大为已经使用的槽数目的两倍。开辟了新空间以后,会把原来哈希表里
253+
不为空槽的数据重新插入到新的哈希表里,插入方式和之前一样。这就是 rehashing 操作。</p>
254+
<h1 id="hashtalbe-adt">HashTalbe ADT</h1>
255+
<p>这里我们来实现一个简化版的哈希表 ADT,主要是为了让你更好地了解它的工作原理,有了它,后边实现起 dict 和 set 来就小菜一碟了。</p>
256+
<h1 id="_4">延伸阅读</h1>
257+
<ul>
258+
<li>《Data Structures and Algorithms in Python》11 章 Hash Tables</li>
259+
<li>《算法导论》第三版 11 章散列表</li>
260+
<li>介绍 c 解释器如何实现的 python dict对象:<a href="http://www.laurentluce.com/posts/python-dictionary-implementation/">Python dictionary implementation</a></li>
261+
</ul>
147262

148263
</div>
149264
</div>

7_哈希表/insert_hash.png

163 KB
Loading

7_哈希表/insert_hash_chaining.png

222 KB
Loading

7_哈希表/quadratic_hash.png

26.4 KB
Loading

7_哈希表/quadratic_result.png

21 KB
Loading

index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ <h2 id="_9">教材</h2>
250250
<p><a href="https://book.douban.com/subject/26979890/">《算法图解》</a>: 图解的形式很适合新手,示例使用的是 python</p>
251251
<p><a href="https://book.douban.com/subject/10607365/">《Data Structures and Algorithms in Python》</a>: 适合对 Python
252252
和算法比较熟悉的同学,或者是有其他语言编程经验的同学。英文版,缺点是书中错误真的很多,代码有些无法运行</p>
253-
<p><a href="https://book.douban.com/subject/20432061/">《算法导论》</a>: 喜欢数学证明和板砖书的同学可以参考,有很多高级主题。使用伪代码</p>
253+
<p><a href="https://book.douban.com/subject/20432061/">《算法导论》</a>: 喜欢数学证明和板砖书的同学可以参考,有很多高级主题。使用伪代码表示,对新手来说不够通俗。目前最新是第三版</p>
254254
<h2 id="_10">讲课形式</h2>
255255
<p>绘图演示+手写板+现场编码</p>
256256
<p>我将使用绘图软件+手写板进行类似于纸笔形式的讲解,边讲边开个终端分成两个窗口,一个用 vim
@@ -373,5 +373,5 @@ <h2 id="_17">本电子书制作和写作方式</h2>
373373

374374
<!--
375375
MkDocs version : 0.17.3
376-
Build Date UTC : 2018-04-20 13:52:10
376+
Build Date UTC : 2018-04-21 10:23:05
377377
-->

search/search_index.json

Lines changed: 37 additions & 7 deletions
Large diffs are not rendered by default.

sitemap.xml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,71 +4,71 @@
44

55
<url>
66
<loc>/</loc>
7-
<lastmod>2018-04-20</lastmod>
7+
<lastmod>2018-04-21</lastmod>
88
<changefreq>daily</changefreq>
99
</url>
1010

1111

1212

1313
<url>
1414
<loc>/0_课程简介之笨方法学算法/why_and_how_to_learn/</loc>
15-
<lastmod>2018-04-20</lastmod>
15+
<lastmod>2018-04-21</lastmod>
1616
<changefreq>daily</changefreq>
1717
</url>
1818

1919

2020

2121
<url>
2222
<loc>/1_抽象数据类型和面向对象编程/ADT_OOP/</loc>
23-
<lastmod>2018-04-20</lastmod>
23+
<lastmod>2018-04-21</lastmod>
2424
<changefreq>daily</changefreq>
2525
</url>
2626

2727

2828

2929
<url>
3030
<loc>/2_数组和列表/array_and_list/</loc>
31-
<lastmod>2018-04-20</lastmod>
31+
<lastmod>2018-04-21</lastmod>
3232
<changefreq>daily</changefreq>
3333
</url>
3434

3535

3636

3737
<url>
3838
<loc>/3_链表/linked_list/</loc>
39-
<lastmod>2018-04-20</lastmod>
39+
<lastmod>2018-04-21</lastmod>
4040
<changefreq>daily</changefreq>
4141
</url>
4242

4343

4444

4545
<url>
4646
<loc>/4_队列/queue/</loc>
47-
<lastmod>2018-04-20</lastmod>
47+
<lastmod>2018-04-21</lastmod>
4848
<changefreq>daily</changefreq>
4949
</url>
5050

5151

5252

5353
<url>
5454
<loc>/5_栈/stack/</loc>
55-
<lastmod>2018-04-20</lastmod>
55+
<lastmod>2018-04-21</lastmod>
5656
<changefreq>daily</changefreq>
5757
</url>
5858

5959

6060

6161
<url>
6262
<loc>/6_算法分析/big_o/</loc>
63-
<lastmod>2018-04-20</lastmod>
63+
<lastmod>2018-04-21</lastmod>
6464
<changefreq>daily</changefreq>
6565
</url>
6666

6767

6868

6969
<url>
7070
<loc>/7_哈希表/hashtable/</loc>
71-
<lastmod>2018-04-20</lastmod>
71+
<lastmod>2018-04-21</lastmod>
7272
<changefreq>daily</changefreq>
7373
</url>
7474

0 commit comments

Comments
 (0)