Lec19-20 Hashing
搜索树的可进步之处
- 树中的元素需要是可比较的,如两个汉字便无法存储在其中
- 时间复杂度为\(\Theta(\log N)\),能不能再进行优化
快一点
WriteItOnTheWall Set
假设我们有一个集合WriteItOnTheWall
对于这种集合,添加元素是很快的,只要有空位就可以添加,但是寻找元素是很慢的
用时间复杂度表示为\(\Theta(1)\)与\(\Theta(N)\)
这种集合的好处是不需要管元素种类,只管往里添加即可
BobaCounter Set
我们将上面的集合优化,将整面墙分为十个部分,标号0-9,对应每个数字的个位数放入相应的集合中。
这样我们的耗时可以减少十分之一
但是这种思路也有问题:
- 会浪费每个分区的空间
- 各个分区元素个数可能会不平衡
- 数字不一定随机
- 其他数据类型该如何处理
哈希表
减少浪费空间
使用链表,每个相同尾数的元素进入后添加一个节点链接到上一个元素后面
处理大量数据
设有N个物品,M个桶,假设平均分,则每个桶里有\(\frac{N}{M}\)个物品,时间复杂度为\(\Theta(\frac{N}{M})\)
若M为常数则时间复杂度可以减至\(\Theta(N)\)
使M随着N的增长而增长,我们可以做到常数时间复杂度。这时我们需要一种方法来根据不同的M将数组分为M组
取模操作
我们可以通过取模做到这一点
- 可以适用于任意的M
- 可以平均分配随机的数
- 使用质数当模数分配结果可以更加独立
增加M
为了保持常数时间复杂度,我们需要始终保持\(\frac{N}{M}\)小于某个常数k
常用方法是:当桶的平均大小大于k,我们增加M
- 当增加M时,我们同时要保证将数重新均分到新的桶中
- 参考ArrayList中的resize,最好的方法是每次double M的大小


综上,若我们有N个元素均分,每个列表的长度为\(\frac{N}{M}\)
- \(\frac{N}{M}\)近乎一个常数
- 故操作的时间复杂度也大约是常数
支持其他数据类型
对于不同数据类型,问题在于存放数据的集合并不负责将对应数据分类。
集合对于int类型的操作最为灵活,故我们需要让集合只作用与int类型
此时其他类型需要依靠自己的类来实现到int的转换
对字符串:有一种方法是将字符串用26进制转换为一个数

通过这种方法我们可以支持string操作
哈希码
对于汉字等拥有庞大字库的字符,若使用unicode码套用该方法,转换出的数字可能会超出数据类型的范围,实际上数字大小随着字符长度是指数增加的
因此我们需要通过减小进制数来将庞大的数字固定在一个范围内,如整型的取值范围等,如java中使用31计算,计算出来的数值即为哈希码,减小范围的代价是可能有重复的哈希码,但是对于有意义的字符串,这种概率还是比较小的
Java转换哈希码的函数如下:
哈希表
我们创建的集合称为哈希表
- 数据通过特定的哈希函数转换成哈希码,保证在整型的取值范围内
- 哈希码之后使用模数被减少到一个合理的下标
在Java中,哈希表最常用来实现set和map
哈希函数
设计哈希函数的原则是让入表的元素平均分配
其中一种重写哈希函数的场景是对类重写equals方法时,为了能让哈希表正常工作,我们也需要重写哈希码方法,来确保若两个元素相等则他们的哈希码一致,若不设置可能相同的元素会进入不同的桶中
不可变类型
不可变类型指在实例化后无法再改变的类型
如int,string等类型
final关键字可以设置一个不可变变量,防止你在设置值后进行修改
- final变量意味着你可以给该变量分配一个值,这之后就不能再修改了
- final关键字对于一个不可变类来说不是必须的

哈希表中,不要改变一个用作键的对象,否则会将自己放在错误的位置