数据结构day6

哈希表

     哈希(hash)存储,也被称为散列存储。就是利用一个哈希函数将关键码字与地址联系起来,这样通过hash函数就能由元素的值找到元素存放的内容,时间复杂度为O(1)。

1 哈希存储的优点及缺点

  • 优点:查找速度极快O(1),与元素个数无关
  • 缺点:(1) 数据存储不均衡;很多时候数据因为相关性都集中于一个小区域内,造成有的地方数据很多,同时其他区域都没有数据(2)地址冲突;哈希函数都是自己定义的,会出现多个数据映射到同一内存区域的情况,这时就会发生地址冲突(3)浪费资源;会有很多区域没有数据,但是不能被哈希存储占用不能被其他程序使用,造成内存的浪费

2 哈希存储举例

例:有数据元素序列(14,23,39,9,25,11),若规定每个元素k的存储地址H(k)=k , H(k)称为散列函数,画出存储结构图。


哈希存储

根据散列函数H(k)=k ,可知元素14应当存入地址为14的单元,元素23应当存入地址为23的单元,……

3 如何进行散列查找

     根据键值可以快读定位到存储地址,键值作为参数输入哈希函数,第一次能快速将数据存储到对应地址,之后每次查找时根据键值也能通过哈希函数很快定位到这个地址,通过比较地址内的数据和要查找的数据是否相同来判断查找是否成功
     很多时候要查找的数据量比哈希地址的范围大很多,这时经过哈希函数映射后不同数据会被映射到同一地址,就会产生冲突。

3.1 地址冲突举例

有6个元素的关键码分别为:(14,23,39,9,25,11)。
选取关键码与元素位置间的哈希函数为H(k)=k mod 7


地址冲突

通过哈希函数计算后有多个键码放到同一个地址的情况。在哈希存储时冲突是不可避免的,只能尽可能减少冲突,这时就要做好以下两点:

  1. 构造好的哈希函数
    (a) 好的哈希函数应该比较简单,这样才能提高转换速度
    (b) 通过所选函数对关键码计算出的地址应该在哈希地址中集中并且大致均匀分布,这样才能减少空间浪费
  2. 制定一个好的解决冲突的方案
    (a) 查找时,如果从哈希函数计算出的地址中查不到关键码,则应当依据解决冲突的规则,有规律地查询其它相关单元。

总结
     哈希函数只是一种映射,所以很灵活,只要使任何关键码的哈西函数值都落在表长允许的范围之内即可。构造一个合适的哈希函数很关键。

制定解决冲突基本要求:

  • 要求一:n个数据原仅占用n个地址,虽然散列查找是以空间换时间,但仍希望散列的地址空间尽量小。
  • 要求二:无论用什么方法存储,目的都是尽量均匀地存放元素,以避免冲突。

同义词:具有相同函数值的两个关键码

4 哈希函数构造方法

     常用的哈希函数主要由以下几种

  • 直接定址法
  • 除留余数法
  • 乘余取整法
  • 数字分析法
  • 平方取中法
  • 折叠法
  • 随机数法

4.1 直接定址法

Hash(key) = a·key + b (a、b为常数)

  • 优点:以关键码key的某个线性函数值为哈希地址,不会产生冲突.
  • 缺点:要占用连续地址空间,空间效率低。
    例.关键码集合为{100,300,500,700,800,900},
    选取哈希函数为Hash(key)=key/100,
    则存储结构(哈希表)如下:


    直接定址法

4.2 除留余数法

Hash(key)=key mod p (p是一个整数)
特点:以关键码除以p的余数作为哈希地址。
关键:如何选取合适的p?p选的不好,容易产生同义词
技巧:若设计的哈希表长为m,则一般取p≤m且为质数
(也可以是合数,但不能包含小于20的质因子)。

4.3 乘余取整法

Hash(key)= [ B( Akey mod 1 ) ]
(A、B均为常数,且0 特点:以关键码key乘以A,取其小数部分,然后再放大B倍并取整,作为哈希地址。
例:欲以学号最后两位作为地址,则哈希函数应为:
H(k)=100(0.01k % 1 )
其实也可以用法2实现: H(k)=k % 100

4.4 数字分析法

特点:选用关键字的某几位组合成哈希地址。选用原则应当是:各种符号在该位上出现的频率大致相同。
例:有一组(例如80个)关键码,其样式如下:


数字分析

讨论:
① 第1、2位均是“3和4”,第3位也只有“ 7、8、9”,因此,这几位不能用,余下四位分布较均匀,可作为哈希地址选用。
② 若哈希地址取两位(因元素仅80个),则可取这四位中的任意两位组合成哈希地址,也可以取其中两位与其它两位叠加求和后,取低两位作哈希地址。

4.5 平方取中法

特点:对关键码平方后,按哈希表大小,取中间的若干位作为哈希地址。(适于不知道全部关键码情况)
理由:因为中间几位与数据的每一位都相关。
例:2589的平方值为6702921,可以取中间的029为地址。

4.6 折叠法

特点:将关键码自左到右分成位数相等的几部分(最后一部分位数可以短些),然后将这几部分叠加求和,并按哈希表表长,取后几位作为哈希地址。
适用于:关键码位数很多,且每一位上各符号出现概率大致相同的情况。
法1:移位法 ── 将各部分的最后一位对齐相加。
法2:间界叠加法──从一端向另一端沿分割界来回折叠后,最后一位对齐相加。
例:元素42751896,
用法1: 427+518+96=1041
用法2: 427 518 96—> 724+518+69 =1311

4.7 随机数法

Hash(key) = random ( key ) (random为伪随机函数)
适用于:关键字长度不等的情况。造表和查找都很方便。

总结:哈希函数是通过自己构造的,上面只是提供了一些比较经典的方法,你也可以构造自己的哈希函数,或者将多个经典的方法一起使用。
:关键字长度不等的情况。造表和查找都很方便。

构造哈希函数的原则:
① 执行速度(即计算哈希函数所需时间);
② 关键字的长度;
③ 哈希表的大小;
④ 关键字的分布情况;
⑤ 查找频率

5 冲突处理的方法

  • 开放定址法(开地址法)
  • 链地址法(拉链法)
  • 再哈希法(双哈希函数法)
  • 建立一个公共溢出区

5.1 开放定址法

设计思路:有冲突时就去寻找下一个空的哈希地址,只要哈希表足够大,空的哈希地址总能找到,并将数据元素存入

5.1.1 线性探测法

Hi=(Hash(key)+di) mod m ( 1≤i < m )其中:
Hash(key)为哈希函数
m为哈希表长度
di 为增量序列 1,2,…m-1,且di=i

关键码集为 {47,7,29,11,16,92,22,8,3},
设:哈希表表长为m=11;
哈希函数为Hash(key)=key mod 11;
拟用线性探测法处理冲突。建哈希表如下:


线性定址法

解释:

  • 47、7是由哈希函数得到的没有冲突的哈希地址;
  • Hash(29)=7,哈希地址有冲突,需寻找下一个空的哈希地址:由H1=(Hash(29)+1) mod 11=8,哈希地址8为空,因此将29存入。
  • 另外,22、8、3同样在哈希地址上有冲突,也是由H1找到空的哈希地址的。
  • 其中3 还连续移动了(二次聚集)

线性探测法的优点:只要哈希表未被填满,保证能找到一个空地址单元存放有冲突的元素;
线性探测法的缺点:可能使第i个哈希地址的同义词存入第i+1个哈希地址,这样本应存入第i+1个哈希地址的元素变成了第i+2个哈希地址的同义词,……,
因此,可能出现很多元素在相邻的哈希地址上“堆积”起来,大大降低了查找效率。

解决方案:可采用二次探测法或伪随机探测法,以改善“堆积”问题。

5.1.2 二次探测法

仍举上例,改用二次探测法处理冲突,建表如下:
Hi=(Hash(key)+di) mod m
其中:Hash(key)为哈希函数
m为哈希表长度,m要求是某个4k+3的质数;
di为增量序列 1^2,-1 ^2,2 ^2,-2 ^2,…,q ^2


二次探测法

注:只有3这个关键码的冲突处理与上例不同:

  • Hash(3)=3,哈希地址上冲突,由
  • H1=(Hash(3)+1 ^2) mod 11=4,仍然冲突;
  • H2=(Hash(3)-1 ^2) mod 11=2,找到空的哈希地址,存入。

5.1.3 伪随机探测法

若di=伪随机序列,就称为伪随机探测法

5.2 链地址法(拉链法)

基本思想:将具有相同哈希地址的记录(所有关键码为同义词)链成一个单链表,m个哈希地址就设m个单链表,然后用一个数组将m个单链表的表头指针存储起来,形成一个动态的结构。

设{ 47, 7, 29, 11, 16, 92, 22, 8, 3, 50, 37, 89 }的哈希函数为:
Hash(key)=key mod 11,
用拉链法处理冲突,则建表如图所示。


链地址法

5.3 再哈希法(双哈希函数法)

(hash1(key) + i * hash2(key)) % TABLE_SIZE
i=1, 2, …,k
hash1(),hash2()是不同的哈希函数,当产生冲突时i++,再重新计算一遍,直到冲突不再发生。
注意:第二个哈希函数结果不能为0,而且第二个哈希函数要覆盖表的每一个单元。

  • 优点:不易产生聚集;
  • 缺点:增加了计算时间。

5.4 建立一个公共溢出区

思路:除设立哈希基本表外,另设立一个溢出向量表。
所有关键字和基本表中关键字为同义词的记录,不管它们由哈希函数得到的地址是什么,一旦发生冲突,都填入溢出表。

6 哈希表的查找和分析

明确:散列函数没有“万能”通式(杂凑法),要根据元素集合的特性而分别构造。
讨论:哈希查找的速度是否为真正的O(1)?
不是。由于冲突的产生,使得哈希表的查找过程仍然要进行比较,仍然要以平均查找长度ASL来衡量。
一般地,ASL依赖于哈希表的装填因子α,它标志着哈希表的装满程度。


ASL

其中0≤α≤1,α 越大,表中记录数越多,说明表装得越满,发生冲突的可能性就越大,查找时比较次数就越多。

总结

  • 散列存储的查找效率到底是多少?
    答:ASL与装填因子α有关!既不是严格的O(1),也不是O(n)
  • “冲突”是不是特别讨厌?
    答:不一定!正因为有冲突,使得文件加密后无法破译!(单向散列函数不可逆,常用于数字签名和间接加密)。利用了哈希表性质:源文件稍稍改动,会导致哈希表变动很大。

你可能感兴趣的:(数据结构day6)