哈希表的查找过程与哈希表的创建过程是一致的。
散列表(Hash table,也叫哈希表),是根据键(Key)而直接访问在内存存储位置的数据结构。也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做哈希函数,存放记录的数组称做哈希表
我一般是用除数余留法对关键字的地址进行分配。
除数余留法
除数余留法是假设哈希表长为m,p是小于等于m的最小素数,则哈希函数为:H(K)=k%p。H(k)是计算哈希地址的。
虽然这样可以求出关键字的哈希地址,但是这时会产生冲突,导致多个关键字进入到一个地址中。
这是我们就要尽量减少冲突的发生,减少冲突的方法有多个,我们这里学习开放地址的法和链地址法。
链地址法
将所有关键字为同义词的记录存储在同一线性单链表中,我们称这种表为同义词子表,在哈希表中只存储所有同义词子表的头指针。
例:
已知一组关键字分别为(32,40,36,53,16,46,71,27,42,24,49,64),哈希表长度为13.
由此可知哈希函数为H(k)=k%13.这时我们要利用哈希函数将关键字的哈希地址算出并填入下表中。
由公式我们可以很容易算出上面关键字的地址,将他们写入下表中,如下图所示。
这时我们也可以得出哈希表的平均查找长度ASL
ASL=(比较的次数*关键字的个数)/关键字总共的个数=(1*7+2*4+3*1)/12=1.5
开放地址法
开放地址法会根据当前的位置计算出下一个位置,将这个冲突的元素挪进来。 如果这下一个位置也被占用了,那么就再计算下一个位置,直到找到一个空的位置。 可以想像,将会有一条虚拟的链条将这些相关的位置串起来。 这个虚拟的链条就好比开链法里面的第二维链表。
开放地址法的公式为:
hi=(h0+di)%m.
h0=H(key)是哈希函数,di为增量序列,m为表长,hi为算出的地址。
增量序列的取值不同,相应的再散列方式也不同。主要有三种方式,这里给出两种散列方式,线性探测再散列和二次探测再散列。
线性探测再散列
di=1,2,3,4.......m-1
这种方法的特点是当冲突发生时,顺序查看表中的下一个单元,直至查找到一个空单元或全表。
例
已知一组关键字分别为(32,40,36,53,16,46,71,27,42,24,49,64),哈希表长度为13.
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
由公式H(k)=k%p,可知H(32)=32%13=6 填入到表中
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
32 |
由公式H(k)=k%p,可知H(40)=40%13=1 填入到表中
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
40 | 32 |
由公式H(k)=k%p,可知H(36)=36%13=10 填入到表中
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
40 | 32 | 36 |
由公式H(k)=k%p,可知H(53)=53%13=1,这时发现冲突,利用公式hi=(h0+di)%m,将53的哈希地址向后移动一位。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
40 | 53 | 32 | 36 |
接下来就按这种方式依次将哈希地址算出,将关键字放到各自的哈希地址上。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
64 | 40 | 53 | 16 | 27 | 42 | 32 | 46 | 71 | 36 | 24 | 49 |
二次探测再散列
d=1(^2),-1(^2),2(^2),-2(^2).....k(^2),-k(^2) (k<=m/2)
这种方法的特点是,当冲突发生时,在表的左右跳跃式探测,比较灵活。
大家可以用上面的例子或其他的例子在下面试一试。