//尽可能让文章通俗易懂//
一、基本概念
散列表:通过将关键码映射到表中某个位置上来存储元素,然后根据关键码用同样的方式直接访问。
(根据给定的关键字来计算关键字在表中的地址)
Hash(key)=Addr
通过散列函数建立了从元素关键码集合到散列表集合的一个映射。
由于关键码集合比散列表结合大得多,经过散列函数计算把不同关键码映射到同一个散列地址,这会产生冲突。
为了尽可能避免冲突要处理这两个问题:
(1)选择计算简单且地址分布均匀的散列函数
(2)拟定解决冲突的方案
二、常见散列函数
构造散列函数应注意:
1)函数定义域必须包括全部需要存储的关键字,而值域依赖散列表的大小或地址范围
2)地址等概率、均匀分布在整个地址空间
3)函数尽量简单,计算地址时间短
1.直接定址法
直接取关键字的某个线性函数值为散列函数,散列函数为H(key)=a*key+b。适用于关键字分布连续的情况
优点:简单,无冲突
缺点:若关键字分布不连续,空位多,会造成存储空间浪费
例:hash(key)=key-940000
序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
关键字 | 942148 | 941269 | 940527 | 941630 | 941805 | 941558 | 942047 |
散列地址 | 2148 | 1269 | 527 | 1630 | 1805 | 1558 | 2047 |
2.除留取余法⭐⭐⭐
已知散列表长为m,取一个不大于m但接近于m的质数p,利用公式H(key)=key%p 把关键字转为散列地址
3.数字分析法
设有n个d位数,每一位可能有r种不同的符号,例如十进制数r=10。这r种不同的符号在各位出现的频率不一定相同。
若散列表的地址范围为[0...m-1],占d位数可选其中几种符号分布最均匀的d位,并乘以一个比例因子(m/r^d),把得到d位数压缩到0~m-1,从而得到记录的散列地址。
适用于已知关键字集合。
序号 | 关键码 | |||||
① | ② | ③ | ④ | ⑤ | ⑥ | |
0 | 9 | 4 | 2 | 1 | 4 | 8 |
1 | 9 | 4 | 1 | 2 | 6 | 9 |
2 | 9 | 4 | 0 | 5 | 2 | 7 |
3 | 9 | 4 | 1 | 6 | 3 | 0 |
4 | 9 | 4 | 1 | 8 | 0 | 5 |
5 | 9 | 4 | 1 | 5 | 5 | 8 |
6 | 9 | 4 | 2 | 0 | 4 | 7 |
7 | 9 | 4 | 0 | 0 | 0 | 1 |
可看到1、2、3 数字分布不均匀,4、5、6数字分布均与
4.平方取中法
取关键字的平方值的中间几位作为散列地址。
适用于关键字集合的每位取值不均匀或均小于散列地址所需的位数
5.折叠法
将关键字分割成位数相同的几部分,然后取这几部分的叠加和作为散列地址
叠加方法:(1)各部分最后一位相加 (2)各部分不折断,沿各部分的分界来回折叠,然后对齐相加,结果作为散列地址
———————————————————————————————————————————————————————
上面介绍了几种散列函数,但是都有各自的缺陷,而且散列函数不可决定避免冲突。所以当有冲突时,如何为产生冲突的关键字寻找下一个空的hash地址 非常重要。
三、处理冲突的方法
1,开放定址法
指可存放新表项的空闲地址既向它的同义表项开放,又向它的非同义表项开放。数学递推公式为:%m 其中i=0,1,2....k(k<=m-1);m表示散列表表长,di为增量顺序
取定某一增量序列后,对应处理方法时确定的
(1)线性探测法
使用某种函数计算出初始散列地址,当发生冲突时,在表中顺次向后寻找下一个空闲位置
%m
例如:有一组元素关键码为37,25,14,36,49,68,57,11,散列表为HT[12],表的大小为m=12,采用散列函数为Hash(x)=x%11
Hash(37)=37%11=4 Hash(25)=25%11=3 Hash(14)=14%11=3 Hash(36)=36%11=3
Hash(49)=49%11=3 Hash(68)=68%11=2 Hash(57)=57%11=2 Hash(11)=11%11=0
关键码 | 37 | 25 | 14 | 36 | 49 | 68 | 57 | 11 |
初始散列地址 | 4 | 3 | 3 | 3 | 5 | 2 | 2 | 0 |
冲突散列地址 | ---- | --- | 3,4 | 3,4,5 | 5,6 | --- | 2,3,4,5,6,7 | --- |
最后存入地址 | 4 | 3 | 5 | 6 | 7 | 2 | 8 | 0 |
探测次数 | 1 | 1 | 3 | 4 | 3 | 1 | 7 | 1 |
得到的散列表如图
11 | 68 | 25 | 37 | 14 | 36 | 49 | 57 |
查找成功的平均查找 长度为 查找到表中已有元素的平均探测次数,它是找到表中各个元素的探测次数的 平均值
查找不成功平均查找长度 为 表中查找不到待查元素,但找到插入位置的平均探测次数,它是要插入新元素时为找到空闲地址的探测次数的平均值,其设计范围包括散列函数可计算出的地址
(2)二次探测法
当di=时称为平方探测法,也称为二次探测法。
使用二次探测法在表中寻找下一个空闲位置的公式为 。m为表的大小,值为4k+3的素数
缺点是不能探测散列表所有单元,至少能探测一半
(3)再散列法
当di=Hash(key)
需要使用两个散列函数,
第一个函数得到的地址发生冲突时,利用第二个函数计算该关键字的地址增量。
i是冲突次数,初始为0,最多经过m-1次探测遍历表中所有位置,回到H0位置
(4)伪随机序列法 当di=伪随机序列时,称为伪随机序列法
2.拉链法
把所有同义词用线性链表存储起来,线性链表由其散列地址唯一标识,Hash存放得是相应同义词单链表的表头指针。
假设散列地址为i的同义词链表的头指针存放在散列表的第i个单元中,因而插入、删除、查找主要在同义词链进行。
例如 关键字序列{19,14,23,01,68,20,84,27,55,11,10,79},散列函数H(key)=key%13
拉链法处理如图
四、散列查找以及性能分析
查找过程与构造散列表的过程基本一致。对于一个给定的关键字key,根据散列函数可计算出其散列地址。
步骤:初始化addr=has(key)
(1)检测查找表中地址为addr的位置上是否有记录,若无记录,返回查找失败;若有记录,与key比较,若想等,则返回查找成功标指,否则执行步骤(2)
(2)用给定的处理冲突方法计算下一个散列地址,并把addr置为此地址,转入(1)
查找效率:散列函数、装载因子、处理冲突的方法
装载因子 ,n为表中记录数,m为散列表长度