Python数据结构与算法55:排序与查找:散列函数设计

:本文如涉及到代码,均经过Python 3.7实际运行检验,保证其严谨性。

本文阅读时间约为6分钟

本节介绍两种散列函数设计方法:折叠法和平方取中法。

散列函数设计:折叠法

折叠法设计散列函数的基本步骤是:

将数据按照位数分为若干段,再将几段数字相加,最后对散列表大小求余,得到散列值。

例如,我们要保存一个电话号码62767255。

先按每两位将这8位数分为4段(62、76、72、55)。

4段数字相加(62+76+72+55=265)。

散列表包括0~11等11个槽,那么就是265/11=1。

所以h(62767255)=1,该电话号码的散列值为1,也就是说,应该存放在1号槽。

有时候折叠法还会包括一个隔数反转的步骤。比如上述的(62、76、72、55)隔数反转为(62、67、72、55),即隔数的76和55分别反转为67和55。

然后再累加(62+67+72+55=256)。

接着对11求余(256%11=3),所以h'(62767255)=3。

一般而言,隔数反转从理论上看并没必要,但是它确实为折叠法得到散列函数提供了一种微调手段,有时得到的结果能更好地符合散列特性。

散列函数设计:平方取中法

平方取中法,首先将数据项做平方运算,然后取平方数的中间两位,再对散列表的大小求余。

例如,用平方取中法对44进行散列。

首先44*44=1936,然后取1936这个数中间的两位——93,再对散列表大小11求余,最后得到44的散列值93%11=5。

平方取中法和求余法散列函数的对比

两种散列函数都是完美散列函数,分散度都很好。只是相比求余法,平方取中法计算量稍大。

数据项 求余法 平方取中法
54       10        3
26        4        7
93        5        9
17        6        8
77        0        4
31        9        6
非数字型数据项的散列函数设计

前面说的都是数字型数据项的散列函数设计。那么对于非数字型数据项,我们如何求其散列值?

对于非数字型数据项,我们可以把数据项中的每个字符看作是ASCII码即可。
如要求cat的散列值,采用如下步骤:

把数据项中的每个字符看作是ASCII码,求其数字:ord('c')=99,ord('a')=96,ord('t')=116。

再将这些整数累加:99+97+116=312。

最后用累加之和对散列表大小求余,得到cat的散列值:312%11=4。

参考代码如下:

def hash(aString, tableSize):
    r = 0
    for pos in range(len(aString)):
        r += ord(aString[pos])
    return r % tableSize

print(hash('cat', 11))

<<<4

当然,这种方法有一个问题,那就是所有的变位词(如cat和tac,组成的字符及分别的数量一样,就是变位词),其散列值是一样的。

为了防止出现这种问题,可以根据字符串所在位置作赋予不同的权重因子,再乘以ord值。

用这种改善后的方法来计算cat的散列值,就有:

ord('c')*1+ord('a')*2+ord('t')*3=641。

641%11=3。

除了上述的散列函数设计方法外,我们还可以设计出更多的散列函数方法,但要坚持的一个基本出发点——散列函数不能成为存储和查找过程中的计算负担。

如果散列函数设计太过复杂,要花费大量的计算资源计算槽号,这样就失去了散列本身的意义,可能还不如简单地进行顺序查找或二分查找。

To be continued.

你可能感兴趣的:(Python数据结构与算法55:排序与查找:散列函数设计)