引言
上面的一篇文章已经为这篇文章做了很好的铺垫,让我们认识了哈希表。那么我们来认识一下什么是扩容。扩容顾名思义就是扩大容量的意思。我们下面会封装3个方法包括,put(新增/修改数据),get(获取数据),remove(删除数据)。上面我们已经提到了先定义一个固定长度的数组。既然是固定长度那么当我们在不知道增加多少的情况下这时候就用到了扩容。本篇文章是基于链地址法来实现的哈希表。
什么情况下扩容呢?
比如常见的情况是loadFactor(填充因子)>0.75的时候,对哈希表进行扩容
为什么需要扩容?
目前,我们是将所有的数据项放在长度为7的数组中
因为我们使用的是链地址法,loadFactor(填充因子)可以大于1,所以这个哈希表可以无限制的插入新数据
但是,随着数据量的增多,每一个index对应的bucket会越来越长,也就造成效率的降低
所以在合适的情况对数组进行扩容,比如扩容两倍
如何扩容?
扩容可以将简单的将容量增大两倍(不是质数吗?质数问题后面在讨论)
但是这种情况下,所有的数据项一定要同时进行修改(重新调用哈希函数,来获取到不同的位置)
比如hashCode=12的数据项,在length=8的时候,index=4 在长度为16的时候呢?index=12
这是一个耗时的过程,但是如果数组需要扩容,那么这个过程是必要的
检测质数
在写方法之前我们需要用到检测质数的方法,这样可以用于数组扩容之后的长度为质数从而解决元素的聚集问题;
那么我们先来看一看普通检测
function isPrimeHigh(num) {
for (let i = 2; i < num; i++) {
if (num % i === 0) {
return false
}
}
return true
}
上面这种方法是可以正常检测的,但是他的效率很慢,如果是一个比较小的数还是很好的,但是如果传进去的是一个大的数字那么就很慢了。
高效的检测质数方法
//利用开方的方法 例如以下的12
// 2 * 6 = 12
// 3 * 4 = 12
// Math.sqrt(num)*Math.sqrt(num)=12
// 4 * 3 = 12
// 6 * 2 = 12
// 循环时只需循环到Math.sqrt(num)(这里通过parseInt函数取得的是3),因为Math.sqrt(num)后面的乘法就相当于Math.sqrt(num)前面反过来一样,所以如果前面的不能被整除后面的一定不能被整除
/* 拿13举例:parseInt(Math.sqrt(13)) = 3;
i=2时 13 % 2 = 1;
i=3时 13 % 3 = 1; 上面的流程没有进到方法,所以返回true*/
function isPrime(num) {
let temp = parseInt(Math.sqrt(num));
for (let i = 2; i <= temp; i++) {
// i=2 12 % 2 = 0 那么直接返回false
if (num % i == 0) {
return false
}
}
return true
}
哈希表方法
删除方法和上图逻辑大体一致
class HashTable {
constructor() {
this.storage = [];
this.count = 0;
this.limit = 7;
}
//获取关键字对应的索引
hashCode(str, size) {
let hashCode = 0;
//秦九韶算法根据传递进来的key值的每个字符当做关键字传进来这样减少冲突
for (let i = 0; i < str.length; i++) {
//37是一个常用的质数
hashCode = hashCode * 37 + str[i].charCodeAt()
}
// 将一个比较大的数压缩为比较小的数字作为索引
return hashCode % size
}
//新增/修改方法
put(key, val) {
let index = this.hashCode(key, this.limit);
let bucket = this.storage[index];
if (bucket && bucket.length > 0) {
for (let i = 0; i < bucket.length; i++) {
let tuple = bucket[i];
if (tuple[0] === key) {
tuple[1] = val;
return
}
}
} else {
bucket = [];
this.storage[index] = bucket;
}
bucket.push([key, val]);
this.count += 1;
if (this.count / this.limit > 0.75) {
this.expansion(Math.floor(this.limit * 2))
}
}
//获取元素
get(key) {
let index = this.hashCode(key, this.limit);
let bucket = this.storage[index];
if (bucket && bucket.length > 0) {
for (let i = 0; i < bucket.length; i++) {
let tuple = bucket[i];
if (tuple[0] === key) {
return tuple[1]
}
}
} else {
return null;
}
}
//删除元素
remove(key) {
let index = this.hashCode(key, this.limit);
let bucket = this.storage[index];
if (bucket && bucket.length > 0) {
for (let i = 0; i < bucket.length; i++) {
let tuple = bucket[i];
if (tuple[0] === key) {
let currentVal = tuple[1];
bucket.splice(i, 1);
this.count -= 1;
if (this.count / this.limit < 0.75 && this.limit > 7) {
this.expansion(Math.floor(this.limit / 2))
}
return currentVal;
}
}
} else {
return null;
}
}
//扩容以及将定义的数组长度转成质数
expansion(newSize) {
let oldStorage = this.storage;
this.storage = [];
this.count = 0;
this.limit = newSize;
while (!this.isPrime(this.limit)) {
this.limit += 1;
}
for (let i = 0; i < oldStorage.length; i++) {
let bucket = oldStorage[i];
if (!bucket || bucket.length < 1) continue;
for (let j = 0; j < bucket.length; j++) {
let tuple = bucket[j];
this.put(tuple[0], tuple[1])
}
}
}
// 判断是否是一个质数
isPrime(num) {
let power = Math.sqrt(num);
for (let i = 2; i <= power; i++) {
if (num % i === 0) {
return false
}
}
return true
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。