目录
1.定义
哈希表(hash table),又称散列表,可根据关键字直接进行访问,由此看出访问速度快
关键字:可以理解为向哈希表中存储的数据
原理:哈希表建立了一种关键字和存储地址之间的直接映射关系,使每个关键字与结构中的唯一存储位置相对应
核心思想:关键字→映射关系→存储地址
由于哈希表可根据关键字直接进行访问,因此在理想情况下,查找的时间复杂度为
之前在C31.【C++ Cont】静态实现单链表文章中提到过哈希表的思想,可以去回顾回顾,这里节选一部分内容:
如果使用标记数组mp优化(哈希表),直接return mp[x],时间复杂度
即关键字x→映射关系mp[ ]→存储地址&mp[x]→访问*(&mp[x])
2.哈希函数定义
将关键字映射成对应的地址的函数(即映射关系),哈希函数又称散列函数
用公式可以表示为Hash(key) == Address
3.哈希冲突
定义
哈希函数可能会把两个或两个以上的不同关键字映射到同一地址,哈希冲突又称散列冲突
起冲突的关键字被称为同义词
例如:统计数组arr中每个数字出现的次数,arr={1,45,1e10+7,3,17}
方法1: 创建一个有1e10+17个元素的数组mp
按照C31.【C++ Cont】静态实现单链表文章的思想去创建数组mp,对于数字x其出现的次数为mp[x],显然数组的空间过大,容易导致栈溢出,不建议使用
方法2:设计一个哈希函数hash(key)==key/7
设计一个数组mp,对于数字x其出现的次数为mp[x/7]
1/7==1
45/7==3
1e10+7/7==4
3/7==3
17/7==3
显然会有哈希冲突因为45/7、3/7和17/7的结果都是3,会映射到同一地址
解决方法:比如/11或者/其他数,但可能根据数组的数字的不同,还会导致哈希冲突,因此哈希冲突不可避免,则需要1.设计一个合适的哈希函数来尽量减少冲突 2.如果有冲突要能有效解决
4.设计哈希函数的常用方法
1.直接定址法
直接取关键字的某个线性函数来计算,例如hash(key)=key或hash(key)=key-c(c为函数)
缺点:若关键字分布不连续,则会造成存储空间的浪费(见上方的例子)
直接定址法适合关键字连续的情况
2.除留余数法
比如hash(key)==key%7,即hash(key)==key%m,要选取一个合适的模数m
选取模数的方法: 取不太接近2的整数次幂的一个质数(竞赛中知道怎么用就行,无需深究原理)
5.处理哈希冲突的常用方法
1.线性探测法
从发生冲突的位置开始依次线性向后探测,直到寻找到下一个没有存储数据的位置为止,如果走到
哈希表尾,那么回绕到哈希表头的位置
例如数组arr={19,30,5,36,13,20,21},哈希函数为hash(key)==key%11,使在key%11的位置存储key,即数组mp[key%11]=key
计算key%11
19%11==8
30%11==8
5%11==5
36%11==3
13%11==2
20%11==9
21%11==10
在key%11的位置存储key的前提:这个位置之前没有存储过任何数字,那就要为这个位置赋一个都取不到的数:INF(infinite 无穷大),如下图
(这里为了演示方便,创建数组mp的大小和数组arr的大小差不多)
hash(19)==8,因此
hash(30)==8,发现mp[8]的位置放过了,因此使用线性探测法:从发生冲突的位置开始依次线性向后探测,直到寻找到下一个没有存储数据的位置为止,发现mp[9]没有存数,因此mp[9]=30
hash(5)==5,hash(36)==3,hash(13)==2因此
hash(20)==9,发现mp[9]的位置放过了,使用线性探测法,发现mp[10]没有存储数字
hash(21)==10, 发现mp[10]的位置放过了,使用线性探测法,走到哈希表尾,那么回绕到哈希表头的位置,存在mp[0]处
注意事项
一般创建的大小是原大小的两倍,防止存储过于密集,线性探测消耗的时间过多
2.链地址法
链地址法中所有的数据不再直接存储在哈希表中,哈希表中存储指针,若没有数据映射这个位置
时,指针为空(访问空指针指向的数据是非法的);若有多个数据映射到这个位置时,则把这些冲突的数据链接成一个链表,"挂"在哈希表这个位置下面
例如:例如数组arr={19,30,5,36,13,20,21,12,24,96},哈希函数为hash(key)==key%11
初始状态下元素都为空指针
若有多个数据映射到这个位置时,则执行链表头插(类似带哨兵位的链表),最终结果为:
缺点:如果所有的数都挂到mp的同一个下标,则查找这些数的时间复杂度为,解决方法:使用红黑树