散列表(既是一种存储方法,也是一种查找方法)
使用散列函数计算,使得关键字与数值有唯一对应一个存储位置,这样在进行查询操作的时候,只需要进行散列计算即可直接找出相应的位置,达到O(1)的查找性能
特点:
- 空间换时间
- 查找时间平均性能O(1) , 空间消耗O(n) ,若使用bitmap可以达到更低的空间
bitmap是什么?
用一个 bit 位来标记某个元素对应的 Value , 而 Key 即是该元素
散列函数构造方法:
- 直接定址法
F(key) = a * key + b
取某个线性函数作为散列函数使用 - 数字分析法
关键字的位数比较多时使用
抽取部分关键字进行散列计算 - 平方取中法
适用于不知道关键字分布,而位数又不是很大的前提下
将某个关键字平方之后,取平方数的中间几个数字 - 折叠法
将关键字从左往右分割成位数相等的几个部分,将这几个部分分别求和并相加,取最后几位作为散列地址 - 除留余数法(最常用)
对于散列表长为m的散列函数公式为:f(key) = key mod p(p <= m)
不仅可以直接取模,而且可以在进行其他运算之后取模
最重要的是选择合适的p,通常是小于或者等于表长的最小指数或者不包含小于20质因子的合数(最好接近m) - 随机数法
使用随机种子,取关键字的随机数作为散列地址
适用于关键字长度不等的时候
最大的问题:冲突(即不同的值通过散列函数计算之后得到了相同的关键字)
如何解决 ?
-
开放定址法
一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的地址总是能被找到,并记录下来
F(key) = (f(key) + d) mod m (d <= m - 1)
通过不断地变化d的值,使散列地址能被找到
这种方法又叫做线性探测法
缺点:
堆积(现象):
不是同义词的两个数值,却要争夺同一个地址
改善:
将d 的取值改为d的平方,不让关键字聚集在某一块区域,这种方法又被称为二次探测法 -
再散列函数法
事先准备多个散列函数,当地址发生冲突时,使用其他的散列函数计算,总有一个函数能把冲突解决掉,但是增加了计算时间 -
链地址法
对每一个散列地址构建链表,每一次计算到相同地址,相当于对链表执行插入操作
特点:有效解决很多冲突的情况
缺点:查找的时候性能在遍历单链表时造成比较大的损耗 -
公共溢出区法
额外准备一个溢出表,存放冲突的数据
查找的时候:每当散列地址计算出来后,先与基本表对比,不相等的情况下去溢出表进行顺序查找O(n) ,在溢出数据比较少的时候,查找性能还算比较高
装填因子: 用于判断产生冲突的可能性大小
公式:α = 填入表中的记录个数 / 散列表长度
α表示散列表的装满程度,填入表中的记录越多,α越大,产生冲突的可能性越大,散列表的平均查找长度取决于装填因子
无论纪录个数有多少,总是可以选择一个合适的装填因子将平均查找长度限定在一个范围内,达到O(1)的时间复杂度