数据结构——散列函数的构造方法
散列函数的构造方法
散列函数的设计是散列表高效运行的基础,一个好的散列函数能让关键字映射后的散列地址分布均匀,从而减少冲突的发生。构造散列函数时,需要结合关键字的特点(如类型、分布范围)和散列表的容量,选择合适的映射规则。常用的散列函数构造方法有多种,每种方法都有其适用场景,下面逐一进行介绍。
1. 构造散列函数的基本原则
在介绍具体方法前,需要明确构造散列函数应遵循的基本原则,这些原则是衡量一个散列函数优劣的标准。
(1)确定性
对于同一个关键字,无论何时、何地计算,散列函数都应返回相同的散列地址。例如,若关键字“202305”第一次计算得到地址5,第二次计算也必须得到5,否则会导致查找混乱。
(2)高效性
散列函数的计算过程应简单快速,避免复杂的运算(如大量乘法、除法),否则会抵消散列表“直接映射”带来的效率优势。例如,简单的取余运算比复杂的多项式求值更适合作为散列函数。
(3)均匀性
这是最核心的原则,即散列函数应尽可能让关键字均匀分布在散列表的各个地址上,避免大量关键字集中映射到少数几个地址(称为“聚集”)。聚集会导致频繁冲突,严重降低查找效率。
2. 常用的散列函数构造方法
(1)直接定址法
直接定址法是最简单的散列函数构造方法,它直接利用关键字或关键字的线性函数作为散列地址,公式为:
H(key)=key或H(key)=a×key+bH(key) = key \quad \text{或} \quad H(key) = a \times key + bH(key)=key或H(key)=a×key+b
其中aaa和bbb是常数(a≠0a \neq 0a=0)。
例如,若要存储学生的年龄信息(关键字为年龄,范围1825),散列表容量设为10(地址09),可选择H(key)=key−18H(key) = key - 18H(key)=key−18:年龄18对应地址0,19对应地址1,…,25对应地址7,地址分布均匀且无冲突。
这种方法的优点是计算简单、无冲突(只要关键字不重复);缺点是仅适用于关键字分布范围较小且连续的情况,若关键字范围大(如10位学号),会导致散列表容量过大,浪费空间。
(2)除留余数法
除留余数法是最常用的散列函数构造方法,它将关键字除以散列表容量mmm,取余数作为散列地址,公式为:
H(key)=keymod mH(key) = key \mod mH(key)=keymodm
例如,散列表容量m=13m=13m=13(通常选质数),关键字key=123key=123key=123,则H(123)=123mod 13=6H(123) = 123 \mod 13 = 6H(123)=123mod13=6(因13×9=117,123-117=6);关键字key=246key=246key=246,则H(246)=246mod 13=246−13×18=246−234=12H(246)=246 \mod 13 = 246 - 13×18=246-234=12H(246)=246mod13=246−13×18=246−234=12。
这种方法的关键是选择合适的mmm:若mmm是合数,可能导致地址分布不均(例如m=10m=10m=10时,偶数关键字都会映射到偶数地址);因此mmm通常选择质数,或不包含小于20的质因数的合数,以减少聚集。除留余数法适用于关键字为整数的情况,且对关键字分布范围无限制,应用广泛。
(3)数字分析法
数字分析法适用于关键字位数较多,且关键字的某几位分布均匀(即各数字出现概率相近)的情况。它从关键字的各位中选取分布均匀的几位,组合成散列地址。
例如,某系统的用户ID是10位数字(如2023051201、2023051202、…),分析发现前6位“202305”是固定前缀,后4位中前两位“12”是月份,分布集中,而后两位“01、02、…、31”是日期,分布均匀(01~31每个数字出现概率相近)。因此可选择后两位作为散列地址,即H(key)=H(key) =H(key)= 关键字的后两位数值。
这种方法的优点是能充分利用关键字的分布特征,地址均匀性好;缺点是需要预先分析关键字的分布,若分布特征不明显则难以应用(如随机生成的关键字)。
(4)平方取中法
平方取中法先将关键字平方,然后取平方数中间的几位作为散列地址。平方运算会使关键字的各位数字都参与运算,中间几位通常能较好地反映原关键字的特征,分布更均匀。
例如,关键字key=123key=123key=123,平方后为1232=15129123^2=151291232=15129,若散列表容量为100(地址0~99),取中间两位“12”,则H(123)=12H(123)=12H(123)=12;关键字key=456key=456key=456,平方后为4562=207936456^2=2079364562=207936,取中间两位“79”,则H(456)=79H(456)=79H(456)=79。
这种方法适用于关键字位数较少,或分布不明显的情况(如随机整数),缺点是计算稍复杂(需平方运算)。
(5)折叠法
折叠法将关键字按散列表地址位数分成若干段,然后将各段叠加(舍去进位)得到散列地址,类似将纸条折叠后求和。根据叠加方式不同,可分为移位折叠(各段末位对齐后相加)和边界折叠(段与段反向对齐后相加)。
例如,关键字key=123456789key=123456789key=123456789,散列表地址为2位(0~99),将关键字分为3段:12、34、56、789(最后一段可短些)。
- 移位折叠:12 + 34 + 56 + 789 = 891,取后两位得H(key)=91H(key)=91H(key)=91;
- 边界折叠:12 + 43(34反向) + 56 + 987(789反向)= 12+43+56+987=1098,取后两位得H(key)=98H(key)=98H(key)=98。
这种方法适用于关键字位数较多(如长字符串、身份证号)且无法通过数字分析法找到均匀分布位的情况,计算简单,地址分布较均匀。
综上,散列函数的构造方法需根据关键字的类型、分布特征和散列表容量选择:直接定址法适合小范围连续关键字,除留余数法适用范围最广,数字分析法依赖关键字分布特征,平方取中法和折叠法适合长关键字或分布不明显的情况。核心目标是通过合理的映射规则,让散列地址尽可能均匀,为减少冲突奠定基础。
