1.散列
概念:散列表(Hashing)又叫哈希表。查找算法复杂度为O(1),常数级别。
查找快的原因:因为事先知道数据项在数据集的什么位置,直接到那个位置去看看数据是否存在即可。
散列表(哈希表):
- 散列表是一种数据集。
- 散列表中的每一个存储位置,称为槽( slot),可以用来保存数据项,每个槽有一个唯一的名称(槽号)。
例如:如下图所示,一个包含11个槽的散列表,槽的名称分别为0~10。在插入数据项之前,每个槽的值都是None,表示空槽。
散列函数(hash function):数据项关于槽号的函数,比如y=2x,一样,数据项=2×槽号,数据项为2时,槽号为1。
有一种常用的散列方法是"求余数",将数据项除以散列表的大小,得到的余数作为槽号。
例如:将数据项:94,26,93,17,77,31保存到散列表中,请设计一个散列函数,数据项为参数,返回整数0~10,作为槽的名称。
答:h(item)=item%11,item为数据项,item%11为槽号
要查找数据项是否在表中,只需要用散列函数根据数据项计算出槽号,然后查找槽号里有没有数据项即可。
可能有两个数据项计算出来放在同一个槽中,这叫做冲突,冲突怎么解决呢?一会儿再看。
2.完美散列函数
概念:给定一组数据项,如果一个散列函数能把每个数据项映射到不同的槽中,那么这个散列函数就可以称为"完美散列函数′
获得完美散列函数的一种方法:扩大散列表容量,大到所有可能出现的数据项都能占据不同的槽。
好的散列函数的特性:
- 冲突最少(近似完美)
- 计算难度低(额外开销少)
- 充分分散数据项(节约空间)
python自带的散列函数库:hashlib:
update()可对任意长数据部分计算
3.散列函数设计
3.1折叠法
基本步骤:
例如:
对电话号码62767255,
将数据项按照位数分为若干段,
可以两位两位分为4段(62、76、72、55)
再将几段数字相加,
得到散列值相加(62+76+72+55=265)
最后对散列表大小求余,
散列表包括11个槽,那么就是265%11=1所以
h(62767255)=1
隔数反转:(微调)
(62,76,72,56)隔数反转为(62,67,72, 65)
3.2平方取中法
例如,
首先将数据项做平方运算,
对44进行散列首先44*44=1936
然后取平方数的中间两位,
然后取中间的93
再对散列表的大小求余
对散列表大小11求余,93%11=5
3.3非数字项
如下图,ord函数把字符串中每个字符转为ASCII码(ord(“c”=99)),再将整数累加,对散列表大小求余。
代码:
变位词:abc 和 bac
如何防止所有变位词返回同一个槽?
如下图,字符串所在位置乘以ord值再相加求余。
4.冲突解决方案
冲突:两个数据项被散列映射到一个槽里
4.1线性探测
解决方法:寻找空槽(开放定址),逐个槽寻找(线性探测)
聚集:线性探测法的一个缺点是有聚集 clustering)的趋势
即如果同一个槽冲突的数据项较多的话,这些数据项就会在槽附近聚集起来。从而连锁式影响其它数据项的插入。
解决方法:再散列,即不是逐个槽寻找,可以空3个槽寻找。叫做跳跃式探测。
跳跃式探测的再散列通式是∶rehash(pos)=(pos+skip)% sizeoftable
skip取值不能被散列表大小整除,否则会产生周期,让很多空槽永远无法被探测到。(可把散列表大小设为素数)