散列技术——数据的查找(三)
之前我们讲到过线性表、有序表、索引表、树的查找,通过将无序的集合转为上述的结构可以实现查找功能。那么现在要用的就是散列表来查找数据,优势是能够直接找到记录地址不需要花时间查找,但是只适合与给定值相等的记录
散列技术
定义:散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储的位置 f(key)
要理解散列技术就必须先理解之前我们是怎么找到数据记录的。
非散列表查找:
用户输入关键字 ——> 找到关键字 ——> 找到和关键字存在一起的记录地址——> 找到数据记录
- 关键字和数据记录地址是两个内存空间,一般是连续的,此时记录地址是真实存在内存里的
- 最花时间的是如何找到关键字
散列表查找:
用户输入关键字 ——> 把关键字代入函数 ——> 计算得出记录地址 ——> 找到数据记录
- 我既不需要再一堆关键字里面找到用户输入的关键字
- 也不需要把记录地址存在内存里
- 就是通过一条对关键字的计算公式得到记录地址
散列技术就是,建立一个把关键字作为自变量,记录地址作为因变量的函数,也就是说它的核心就是函数关系,至于自变量和因变量是什么,都不太重要
散列函数(哈希函数)、散列表(哈希表)
捋一下这几个概念:
散列函数,也称哈希函数,代表关键字和记录地址的对应关系f
散列表,也称哈希表,表示的是采用散列技术将记录存储在的一块连续的存储空间——即为数据记录本身存的地方
散列技术适合什么问题?
所谓查找给定值相等的记录就是,我给什么值,就找到它的那条唯一记录。
而不是一个定值有很多条对应的记录(例如给“男”这一定值,查找班里的人,会发现有很多记录,也就是说给定的值不能是次关键字),或者说是范围性的查找
散列函数
散列函数的作用就是起一个映射关系,最理想的散列函数能够使得计算过程简单、散列地址分布均匀(为了尽最大努力减少冲突)
下面方法都可以从五方面来考虑他们的优劣:
- 计算散列地址所需要的时间(计算方法复不复杂?)
- 关键字长度(普遍位数的是比较多?关键字与关键字长度差别较大?)
- 散列表的大小
- 关键字的分布情况(分布是否均匀?)
- 记录查找的频率(查找是否频繁?)
1.直接定址法
直接把关键字与其他常数进行简单运算得到地址
2.数字分析法
它关键点在于:抽取
数字分析法就是从关键字中抽取出某个部分作为记录地址
3.平方取中法、折叠法
都是对关键字进行变换的方法,这两种方法的效果是:对关键字位数进行增减之后抽取几位作为记录地址
4.除留余数法
(常用mod方法来控制事物如果超过一定长度后进行轮回)
在这里,通过对关键字取模来映射,那么地址就不会大于所设定的模的数。
5.随机数法
f(key) = random(key),虽然这里的random是随机函数,但是它是伪随机的——同样的随机种子会有同样的值
解决冲突的办法
解决冲突就是为了让两个本要存同一个地址的数据都能被找到不覆盖彼此,办法有
- 开放定址法、再散列法(探测空的位置)
思路:如果冲突,探测找到一个空的位置,把数据放到这个位置。探测的方法有几类:线性探测(找下一个空的位置)、二次探测(往前往后替换找空的位置)、随机探测(random函数)、换一个散列函数再尝试(再散列的方法,再散列不是指二次散列,而是上一个散列函数计算出来的冲突了,那么换一个新的散列函数重新尝试,自变量都是放原来的key)
- 链地址
思路:不换地址,既然冲突了就放在一起,通过链表的形式
image.png - 公共溢出区
思路:既然冲突了,那么就统一把冲突的放到一个地方,在散列表里面找不到就在这里找
散列表的操作
散列表是一种新的数据结构,它的特点是怎么存的我就怎么找,存储和查找的方法都是相同的,因此我们存的时候使用上面哪一种散列函数和冲突解决办法,在查找关键字的时候也是用同样的方法。
平均查找的长度取决于装填因子,而不取决于查找集合中记录个数
装填因子α=已填入表中个数/散列表长度。它标志着散列表的装满程度,装得越多当然冲突的可能就越大啦。如果使得装填因子较小,那就是一个利用空间换时间的例子了。