From a5552b2b69219fa39b0f541dbedebb5136cd4889 Mon Sep 17 00:00:00 2001 From: Shine wOng <1551885@tongji.edu.cn> Date: Tue, 6 Aug 2019 21:20:31 +0800 Subject: [PATCH] modify some errors in hash.md. --- thu_dsa/chp9/hash.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/thu_dsa/chp9/hash.md b/thu_dsa/chp9/hash.md index bbaea67..39fc7a6 100644 --- a/thu_dsa/chp9/hash.md +++ b/thu_dsa/chp9/hash.md @@ -7,7 +7,7 @@ Conclusions on HashTable 据邓公所说,散列是一种思想。与已经学过的其他数据结构相比较,向量是采用循秩访问(call by rank)的访问方式,列表是采用循位置访问(call by position)的访问方式,二叉搜索树是采用循关键码访问(call by key)的访问方式,散列与他们都不一样,是采用循值访问(call by value)的访问方式。 -举个例子,你现在身处同济大学嘉定校区,四周是一片荒芒,这个时候你想回家了。沿世界上所有的街道一间一间房找过去,这是循秩访问;你记得你家是住在四川省成都市某街道多少号,然后你可以依次先到四川省,再到成都市,再到某条街道,然后找到你家,这是循关键码访问;而循值访问,则是你通常会采用的方法——你根本不用去回想我家的地址是多少,你知道它就在那里,就在家这个词刚刚出现在你的脑海中的时候。想到家乡,你想到的不是地址或者一串数字,而是一个生动的影像,包含它的环境,四周的风物,已经曾经的朋友。这就是循值访问。 +举个例子,你现在身处同济大学嘉定校区,四周是一片荒芒,这个时候你想回家了。沿世界上所有的街道一间一间房找过去,这是循秩访问;你记得你家是住在四川省成都市某街道多少号,然后你可以依次先到四川省,再到成都市,再到某条街道,然后找到你家,这是循关键码访问;而循值访问,则是你通常会采用的方法——你根本不用去回想我家的地址是多少,你知道它就在那里,就在家这个词刚刚出现在你的脑海中的时候。想到家乡,你想到的不是地址或者一串数字,而是一个生动的影像,包含它的环境,四周的风物,以及曾经的朋友。这就是循值访问。 可以看到,相对于其他的访问方式,循值访问是将被访问对象的数值,与它在容器中的位置之间,直接建立了一个映射关系,从而对于任何对象的基本操作(访问,插入,删除)都只需要常数$O(1)$的时间,达到了最理想的境地。这就是人类需要散列的原因,你无法不被如此的诱惑所吸引。 @@ -17,7 +17,7 @@ Conclusions on HashTable 也就是说,对于完美散列,其中的每一个值,都可以唯一地映射到散列表中的一个位置,既无空余,亦无重复。从映射角度来看,完美散列是一个单射,同时也是一个满射。Bitmap就是完美散列的一个例子。 -可以看出,完美散列实际中并不常见,在大多数的情形下,关键码的取值是远远大于词条的个数的,设关键码的取值为$[0, R)$, 词条的个数为$N$,则$R >> N$。设散列表的大小为$M$,此时,从定义域$[0, R)$到值域$[0, M)$的映射不可能是单射,即不可避免地会出现不同的关键码映射到散列表中的同一个位置,即所谓冲突。因此就需要合理地选择这一个映射关系,即散列函数,使冲突出现的可能性最小;同时还应该事先约定好一旦出现这种冲突,应该采取的解决访问。这两个问题将在下面重点讨论,即散列函数的设计与冲突解决方案。 +可以看出,完美散列实际中并不常见,在大多数的情形下,关键码的取值是远远大于词条的个数的,设关键码的取值为$[0, R)$, 词条的个数为$N$,则$R >> N$。设散列表的大小为$M$,此时,从定义域$[0, R)$到值域$[0, M)$的映射不可能是单射,即不可避免地会出现不同的关键码映射到散列表中的同一个位置,即所谓冲突。因此就需要合理地选择这一个映射关系,即散列函数,使冲突出现的可能性最小;同时还应该事先约定好一旦出现这种冲突,应该采取的解决方案。这两个问题将在下面重点讨论,即散列函数的设计与冲突解决方案。 ## 散列函数的设计 @@ -77,9 +77,9 @@ $$ 除此以外,还有平方取中法,即对于关键码$key$取平方,然后截取中间的几位来作为散列地址。之所以选择中间的几位,是因为中间的几位是受到了原来的关键码更多数位的影响;相对于取高位数字(只受到原关键码高位数字影响)或者低位数字(只受到原关键码低位数字影响),取中间位数综合了更多位数的影响,因此随机性、均匀性更强,更不易出现冲突。 -此外,还有折叠法往复折叠法。 +此外,还有折叠法,往复折叠法。 -为了保证经过这些方法得到的值仍然落在散列空间以内,通常还都需要对散列表长度$M$在取余。 +为了保证经过这些方法得到的值仍然落在散列空间以内,通常还都需要对散列表长度$M$再取余。 > 随机数法 @@ -134,11 +134,11 @@ $$ $$ 由于各次试探的位置到起始位置的距离,以平方速率增长,故称为平方试探法。 -与线性试探法比较,平方试探法可以理解为迅速离开发生冲突的“是非之地”,以平方的速率迅速跳离关键码聚集的区段,因此可以在一定程度上缓解汇聚的线性。 +与线性试探法比较,平方试探法可以理解为迅速离开发生冲突的“是非之地”,以平方的速率迅速跳离关键码聚集的区段,因此可以在一定程度上缓解汇聚的现象。 可是,关于平方试探法,我们不难提出一些问题,比如平方试探法果真可以覆盖整个散列表吗?是否存在散列表本来有空桶,却无法被探测到的现象? -这种情况是存在的,可以自己举一些例子要验证一下。不过,只要散列表长度$M$为素数,并且装填因子$\lambda \le 50%$,则平方试探法迟早必然会终止于某个空桶,即$n^2 % M$的取值恰好有ceil(\frac{M}{2})$种,并且恰好由查找链的前$ceil(\frac{M}{2})$取遍。以下给出证明: +这种情况是存在的,可以自己举一些例子要验证一下。不过,只要散列表长度$M$为素数,并且装填因子$\lambda \le 50\%$,则平方试探法迟早必然会终止于某个空桶,即$n^2 \% M$的取值恰好有$ceil(\frac{M}{2})$种,并且恰好由查找链的前$ceil(\frac{M}{2})$取遍。以下给出证明: 设$a < b < ceil(\frac{M}{2})$,并且第$a$次试探与第$b$次试探冲突,即$a^2, b^2$是$M$的同余类 @@ -153,7 +153,7 @@ $$ > 双向平方试探法 -根据上面的分析,在$M$为素数并且装填因子$\lambda < 50%$的时候,单向平方试探法可以保证试探必然终止。一个自然的想法是,另一半的桶,是否也可以利用起来呢?这就是双向平方探测法。 +根据上面的分析,在$M$为素数并且装填因子$\lambda < 50\%$的时候,单向平方试探法可以保证试探必然终止。一个自然的想法是,另一半的桶,是否也可以利用起来呢?这就是双向平方探测法。 双向平方探测法,就是在发生冲突时,依次向前向后进行平方探测,即 $$ @@ -162,7 +162,7 @@ $$ 这种方法看似是把两侧的桶都利用起来了,但是我们也不禁产生一个问题,即这双向的探测是否是相互独立呢?它们之间除了零,是否还有其他公共的桶? -答案是,是存在不独立的情况的,并且这种情况还相当的多,也可以自己举几个例子来看一下。但是,如果去散列表长取做素数,并且$ M = 4k + 3$,则必然可以保证查找链的前$M$项都是互异的,以下来证明这个结论。 +答案是,是存在不独立的情况的,并且这种情况还相当的多,也可以自己举几个例子来看一下。但是,如果散列表的长度取做素数,并且$ M = 4k + 3$,则必然可以保证查找链的前$M$项都是互异的,以下来证明这个结论。 这里,我们首先需要提到费马的双平方定理,即任意素数$p$可以表示为两个正整数的平方和,当且仅当$p = 4k + 1$。 @@ -181,7 +181,7 @@ $$ $$ a^2 + b^2 \equiv 0 \ (mod \ M) $$ -即$a^2 + b^2$可以整除$M$。由于$M = 4k + 3$且为素数,所以$a^2 + b^2$可以整除$m^2$,但是$a^2 + b^2 < M^2$,所以假设不成立,故原命题得证。 +即$a^2 + b^2$可以整除$M$。由于$M = 4k + 3$且为素数,所以$a^2 + b^2$可以整除$M^2$,但是$a^2 + b^2 < M^2$,所以假设不成立,故原命题得证。 > 随机试探法(pseudo-random probing) @@ -191,6 +191,6 @@ $$ 顾名思义,设置一个二级散列函数来确定试探位置,即 $$ -[hash(key) + j \times hash_2(key)] % M +[hash(key) + j \times hash_2(key)] \% M $$ 其他的方法其实都可以视作再散列法的一个实例。