From 52c9dfa846b9575c3727f815dd1f18f576060783 Mon Sep 17 00:00:00 2001 From: Shine wOng <1551885@tongji.edu.cn> Date: Tue, 6 Aug 2019 17:16:20 +0800 Subject: [PATCH] Create hash.md, not finished yet. --- thu_dsa/chp9/hash.md | 50 ++++++++++++++++++++++++++++++++++++++++++++ thu_dsa/hash.md | 14 ------------- 2 files changed, 50 insertions(+), 14 deletions(-) create mode 100644 thu_dsa/chp9/hash.md delete mode 100644 thu_dsa/hash.md diff --git a/thu_dsa/chp9/hash.md b/thu_dsa/chp9/hash.md new file mode 100644 index 0000000..0cccb6c --- /dev/null +++ b/thu_dsa/chp9/hash.md @@ -0,0 +1,50 @@ +Conclusions on HashTable +======================== + +## 散列的基本概念 + +> 什么是散列?为什么需要散列? + +据邓公所说,散列是一种思想。与已经学过的其他数据结构相比较,向量是采用循秩访问(call by rank)的访问方式,列表是采用循位置访问(call by position)的访问方式,二叉搜索树是采用循关键码访问(call by key)的访问方式,散列与他们都不一样,是采用循值访问(call by value)的访问方式。 + +举个例子,你现在身处同济大学嘉定校区,四周是一片荒芒,这个时候你想回家了。沿世界上所有的街道一间一间房找过去,这是循秩访问;你记得你家是住在四川省成都市某街道多少号,然后你可以依次先到四川省,再到成都市,再到某条街道,然后找到你家,这是循关键码访问;而循值访问,则是你通常会采用的方法——你根本不用去回想我家的地址是多少,你知道它就在那里,就在家这个词刚刚出现在你的脑海中的时候。想到家乡,你想到的不是地址或者一串数字,而是一个生动的影像,包含它的环境,四周的风物,已经曾经的朋友。这就是循值访问。 + +可以看到,相对于其他的访问方式,循值访问是将被访问对象的数值,与它在容器中的位置之间,直接建立了一个映射关系,从而对于任何对象的基本操作(访问,插入,删除)都只需要常数$O(1)$的时间,达到了最理想的境地。这就是人类需要散列的原因,你无法不被如此的诱惑所吸引。 + +> 完美散列 + +在时间与空间性能上均达到完美的散列,称为完美散列。 + +也就是说,对于完美散列,其中的每一个值,都可以唯一地映射到散列表中的一个位置,既无空余,亦无重复。从映射角度来看,完美散列是一个单射,同时也是一个满射。Bitmap就是完美散列的一个例子。 + +可以看出,完美散列实际中并不常见,在大多数的情形下,关键码的取值是远远大于词条的个数的,设关键码的取值为$[0, R)$, 词条的个数为$N$,则$R >> N$。设散列表的大小为$M$,此时,从定义域$[0, R)$到值域$[0, M)$的映射不可能是单射,即不可避免地会出现不同的关键码映射到散列表中的同一个位置,即所谓冲突。因此就需要合理地选择这一个映射关系,即散列函数,使冲突出现的可能性最小;同时还应该事先约定好一旦出现这种冲突,应该采取的解决访问。这两个问题将在下面重点讨论,即散列函数的设计与冲突解决方案。 + +## 散列函数的设计 + +> 散列函数的设计方案?什么是好的散列函数? + +前面提到,从词条空间到地址空间的映射,即散列函数,绝对不可能是单射,冲突是一定不可能避免的,但是好的散列函数应该保证尽可能地少出现冲突。由此,可以提炼出散列函数的几个设计指标。 + ++ 确定性。散列函数确定的条件下,同一个关键码应该总是映射到同一个地址,这样才满足一个函数的定义。 ++ 快速性。是指散列地址的计算过程要尽可能快,要能在常数时间内完成。 ++ 满射。好的散列函数最好是一个满射,这样可以充分利用散列空间,尽可能地减少冲突的发生。 ++ 均匀性。也是为了减少冲突的发生,关键码被映射到各个散列地址的概率应该接近于$1/M$,这样可以防止汇聚(clustering)现象的发生,即关键码只被映射到少数的几个散列地址,在局部加剧散列冲突,全局的散列空间也没有得到充分地利用。 + +### 几个散列函数的实例 + +> 除余法(division method) + +除余法的整体思路非常简单,即用关键码的值对散列表的长度$M$取余,即$hash(key) = key mod M$,这样可以将关键码映射到整个散列空间上。 + +这里问题的关键在于散列表长度$M$的选择。考虑有一组数据,其中的关键码的固定步长$S$变化(实际中的数据往往就是这种形式的,而不是随机的,例如for循环一般就是固定步长的数据)。根据上面对散列函数设计要求的分析,我们是希望散列函数可以尽可能地减少冲突。设第$i$个数据与第$j$个数据的关键码发生冲突,即 +$$ +S\times i \equiv S\times j\ (mod M) +$$ +即$Si$与$Sj$是同余类,所以 +$$ +S(j - i) \equiv 0 \ (mod M) +$$ +由此可解得 +$$ +j - i = \frac{M}{gcd(M, S)} +$$ \ No newline at end of file diff --git a/thu_dsa/hash.md b/thu_dsa/hash.md deleted file mode 100644 index 7557b3b..0000000 --- a/thu_dsa/hash.md +++ /dev/null @@ -1,14 +0,0 @@ -Conclusions on Hashing -====================== - -## 散列函数的设计 -散列函数无非一个映射,其功能无非是将词条空间中的元素映射到散列表地址空间,其中前者远远大于后者,所以绝不可能是一个单射 -好的散列函数 - - 确定性:同一关键码总是被映射到同一地址 - - 快速 - - 满射:充分利用散列空间 - - 均匀:避免汇聚clustering。 -散列函数 - - 除余法 - - MAD法 - - 折叠法