目录
一. 哈希函数
1.快速的计算
哈希表的主要优点是它的速度,提高速度的一个办法就是让哈希函数中尽量少有乘法和除法。因为它们的性能是比较低的。
在前面, 我们计算哈希值的时候使用的方式cats = 3*27³+1*27²+20*27+17= 60337
这个表达式其实是一个多项式: a(n)xn+a(n-1)x(n-1)+…+a(1)x+a(0)
现在问题就变成了多项式有多少次乘法和加法:
乘法次数: n+(n-1)+…+1=n(n+1)/2
加法次数: n次
多项式的优化: 霍纳法则。
通过如下变换我们可以较快的算法,即
Pn(x)= anx n+a(n-1)x(n-1)+…+a1x+a0=((…(((anx +an-1)x+an-2)x+ an-3)…)x+a1)x+a0,
这种求值的方法称为霍纳法则,变换后的乘法次数: N次,变换后的加法次数: N次.
如果使用大O表示时间复杂度的话, 则直接从O(N²)降到了O(N).
2.均匀的分布
在设计哈希表时,为了处理映射到相同下标值的情况可以使用链地址法或者开放地址法。为了提高效率,最好让数据在哈希表中均匀分布。
因此, 我们需要在使用常量的地方, 尽量使用质数.
质数的使用:
- 哈希表的长度。
- N次幂的底数(上例中使用的是27)
哈希表的长度使用质数:
这个在链地址法中的重要性不是特别明显,明显的是在开放地址法中的再哈希法中.
再哈希法中质数的重要性:
假设表的容量不是质数,例如:表长为15(下标值0~14)。有一个特定关键字映射到0,步长为5。探测序列是0 - 5 - 10 - 0 - 5 - 10, 依次类推循环。算法只探测这三个单元,如果这三个单元已经有了数据,那么会一直循环下去,直到程序崩溃。
如果容量是一个质数,比如13。探测序列是0 - 5 - 10 - 2 - 7 - 12 - 4 - 9 - 1 - 6 - 11 - 3, 这样下去。不仅不会产生循环,而且可以让数据在哈希表中更加均匀的分布。
链地址法中质数没有那么重要, 甚至在Java中故意是2的N次幂
二、哈希函数的实现
设计哈希函数
1.将字符串转化为较大的数字:hashCode
2.将大的数字hashCode压缩到数组范围(大小)之内
function hashFunc(str,size){
//1.定义hashCode变量
var hashCode = 0
//2.霍纳算法,来计算hashCode的值
//cats -> Unicode编码
for(var i=0; i<str.length; i++){
hashCode = 37*hashCode + str.charCodeAt(i)
}
//3.取余操作
var index = hashCode % size
return index
}
测试哈希函数:
alert(hashFunc('rty',8))//7
alert(hashFunc('bds',8))//1
alert(hashFunc('kyu',8))//5
alert(hashFunc('mbc',8))//2
三、哈希表实现
采用链地址法来实现哈希表
实现的哈希表(基于storage的数组)每个index对应的是一个数组(bucket)。(基于链表也可以)
bucket中存放key和value,继续使用一个数组
最终哈希表的数据格式是这样: [[ [k,v], [k,v], [k,v] ] , [ [k,v], [k,v] ], [ [k,v]