哈希表应用

哈希表-查询O(1)复杂度的利器。

理论上说,当输入规模够大时是不可能没有冲突的,因为两个域要有映射,且结果域是远远小于输入域的,选取一个好的哈希函数固然重要,但是解决冲突的方法也是必不可少。

这里先普及一些东西:哈希函数,在随机大样本测试下,基于概率学的研究,每个桶内部的元素个数是差不多的。

为了避免冲突,我们有一些神奇的方法:

1)以int-4字节数据为例,将数据转换为二进制,奇数位与偶数位分离,各自放在新的32位整形的低16位上,再异或。

2)选取2的次幂的位置,与剩下的二进制进行异或。

3)对素数取模。

总之,只要操作方式足够随机,或是哪怕不随机,但是选取的元素间毫无关系可言【例如二进制下不同的位】,就可以称为一个好的哈希函数。

解决冲突的方式:

1.开放定址   哈希到冲突位置后,给定可变或不变步长,向后寻找空位置。

2.链表法  在桶后面接上链表

优化:据说JVM里面的hash,是在桶后面建红黑树

3.再hash  造出足够多的hash函数,冲突则将hash(i)函数的结果用hash(i+1)函数再计算。

如何造出那么多hash:多hash的前提是,hash不能与任意一个hash有相关性。

现假设我们有两个非相关hash,hash1和hash2,我们可以用加权和的方式造出无穷个hash:

hash3=hash1+hash2,hash4=hash1+2*hash2,也就是hash(i)=i*hash1+hash2,hash3虽然看起来和hash1和hash2有关,但是由于hash1、hash2不相关,因此他们加权和的结果和各自都不存在相关性。

要求设计一种结构,增删改查都可以做到O(1),并且还可以等概率返回其中的一个元素。

查询要O(1)?那毫无疑问就是hash了,问题是hash如何做到等概率返回一个元素呢?

遍历:O(n)。

遍历的目的是什么:我们需要知道有多少个元素。

遍历如何实现等概率返回:遍历的同时将数据打到一个数组A里,然后用随机函数rand()生成一个1~n【注:由于这个结构支持增删,所以n是可变的】的index,然后返回A[index]。

避免遍历:如果我们一开始就给这些元素index,会怎么样?

我们用一个hash来存储index->value对,同时用size记录下当前的最大索引,这样rand()生成1~size之间的数的时候就不需要遍历就可以知道长度了,同时,复杂度满足O(1)的要求。

那么删除呢:我们的index是从1开始按照步长为1递增的。我们很有可能把不是结尾元素的第i号元素删除了,那么这个时候i对应的value就会空掉,此时必须经过多次rand(),一旦删除元素过多,由于index是不断递增的,碰到空的几率会越来越大,需要的rand次数就会变多。

实际上,当我们删除一个元素造成空洞的时候,完全可以将最后的元素调整到空洞位置。

查询由于我们给的是value,要查是否存在,所以必须要有一个hash寸的是value->index,而等概率返回则要求我们需要一个index->value的hash。

总结:设计hash1:value->index,hash2:index->value

查询:直接用value在hash1内查询即可

改动:给定一个value,在hash1中找到他的索引,再根据这个索引在hash2中找到它,改两个hash的value域即可。

增加:hash1.insert(value,++size);hash2.insert(size,value)。

删除:

A.删除的是尾元素,直接删除

B.得到hash2中size对应的value',将hash2和hash1中删除位置的value变更为value'。

猜你喜欢

转载自blog.csdn.net/qq_39304630/article/details/82255632