红魔咖啡馆

头发越掉越多,头发越掉越少

0%

【CS61B】Lec19-20 Hashing

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关键字对于一个不可变类来说不是必须的

不可变类

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