Hash Table
散列表(hash table)也被称为哈希表,它是一种根据键(key)来存储值(value)的特殊线性结构。
常用于迅速的无序单点查找,其查找速度可达到常数级别的O(1)。
散列表数据存储的具体思路如下:
- 每个value在放入数组存储之前会先对key进行计算
- 根据key计算出一个重复率极低的指纹
- 根据这个指纹将value放入到数组的相应槽位中
同时查找的时候也将经历同样的步骤,以便能快速的通过key查出想要的value。
这一存储、查找的过程也被称为hash存储、hash查找。
如图所示:
我们注意观察,其实散列表中的每一个槽位不一定都会被占据,它是一种稀疏的数组结构,即有许多的空位,并不像list那种顺序存放的结构一样必须密不可分,这就导致了散列表无法通过index来进行value的操作。
散列表在Python中应用非常广泛,如dict底层就是散列表实现,而dict也是经历了上述步骤才将key-value进行存入的,后面会进行介绍。
名词释义
在学习Hash篇之前,介绍几个基本的相关名词:
- 散列表(hash table):本身是一个普通的数组,初始状态全是空的
- 槽位(slot、bucket):散列表中value的存储位置,用来保存被存入value的地方,每一个槽位都有唯一的编号
- 哈希函数(hash function):如图所示,它会根据key计算应当将被存入的value放入那一个槽位
- 哈希值(hash value):哈希函数的返回值,也就是对数据项存放位置的结算结果
还有2个比较专业性的词汇:
-
散列冲突:打个比方,k1经过hash函数的计算,将v1存在了1号槽位上,而k22也经过了hash函数的计算,发现v2也应该存在1号槽位上。
现在这种情况就发生了散列冲突,v2会顶替v1的位置进行存放,原本1号槽位的存放数据项会变为v2。
-
负载因子:说白了就说这个散列表存放了多少数据项,如11个槽位的一个散列表,存放了6个数据项,那么该散列表的负载因子就是6/11
哈希函数
如何通过key计算出value所需要插入的槽位这就是哈希函数所需要思考的问题。
求余哈希法
如果我们的key是一串电话号码,或者身份证号,如436-555-4601:
- 取出数字,并将它们分成2位数(43,65,55,46,01)
- 对它们进行相加,得到结果为210
- 假设散列表共有11个槽位,现在使用210对11求余数,结果为1
那么这个key所对应的value就应当插入散列表中的1号槽位
平方取中法
平方取中法如下,现在我们的key是96:
- 先计算它的平方值:96^2
- 平方值为9216
- 取出中间的数字:21
- 假设散列表共有11个槽位,现在使用21对11求余数,结果为10
那么这个key所对应的value就应当插入散列表中的10号槽位
字符串求值
上面举例的key都是int类型,如果是str类型该怎么做?
我们可以遍历这个str类型的key,并且通过内置函数ord()来将它字符转换为int类型:
>>> k = "hello"
>>> i = 0
>>> for char in k:
i += ord(char)
>>> i
532
然后再将其对散列表长度求余,假设散列表共有11个槽位,现在使用532对11求余数,结果为4
那么这个key所对应的value就应当插入散列表中的4号槽位。
字符串问题
如果单纯的按照上面的方式去做,那么一个字符完全相同但字符位置不同的key计算的hash