认识哈希表
哈希表通常是基于数组实现的,但是相对于数组,它存在更多优势:
- 哈希表可以提供非常快速的 插入-删除-查找 操作。
- 无论多少数据,插入和删除值都只需接近常量的时间,即 O(1) 的时间复杂度。实际上,只需要几个机器指令即可完成。
- 哈希表的速度比树还要快,基本可以瞬间查找到想要的元素。
- 哈希表相对于树来说编码要简单得多。
哈希表同样存在不足之处:
- 哈希表中的数据是没有顺序的,所以不能以一种固定的方式(比如从小到大 )来遍历其中的元素。
- 通常情况下,哈希表中的
key
是不允许重复的,不能放置相同的key
,用于保存不同的元素。
封装哈希表
哈希表常见操作
put(key, value)
插入或修改操作。get(key)
获取哈希表中特定位置的元素。remove(key)
删除哈希表中特定位置的元素。isEmpty()
如果哈希表中不包含任何元素,返回trun
,如果哈希表长度大于 0 则返回false
。size()
返回哈希表包含的元素个数。resize(value)
对哈希表进行扩容操作。
哈希函数的简单实现
首先使用霍纳法则计算 hashCode 的值,通过取余操作实现哈希化,此处先简单地指定数组的大小。
hashFn(string, limit = 7) {
// 自己采用的一个质数(无强制要求,质数即可)
const PRIME = 31;
// 1、定义存储 hashCode 的变量
let hashCode = 0;
// 2、使用霍纳法则(秦九韶算法),计算 hashCode 的值
for (let item of string) {
hashCode = PRIME * hashCode + item.charCodeAt();
}
// 3、对 hashCode 取余,并返回
return hashCode % limit;
}
选择质数作为哈希表容量
质数判断
1 不是质数
-
方法一:针对质数的特点:只能被 1 和 number 整除,不能被 2 ~ (number-1)整除。遍历 2 ~ (num-1) 。
这种方法虽然能实现质数的判断,但是效率不高。
function isPrime(number) { if (number <= 1) return false; for (let i = 2; i < number; i++) { if (number % i === 0) { return false; } } return true; }
- 方法二:只需要遍历 2 ~ num 的平方根即可。该方法性能较好。
function isPrime(number) { if (number <= 1 || number === 4) return false; const temp = Math.ceil(Math.sqrt(number)); for (let i = 2; i < temp; i++) { if (number % i === 0) { return false; } } return true; }
哈希表完整实现
class HashTable {
constructor() {
this.storage = []; // 哈希表存储数据的变量
this.count = 0; // 当前存放的元素个数
this.limit = 7; // 哈希表长度(初始设为质数 7)
// 装填因子(已有个数/总个数)
this.loadFactor = 0.75;
this.minLoadFactor = 0.25;
}
// getPrime(number) 根据传入的 number 获取最临近的质数
getPrime(number) {
while (!isPrime(number)) {
number++;
}
return number;
}
// put(key, value) 往哈希表里添加数据
put(key, value) {
// 1、根据 key 获取要映射到 storage 里面的 index(通过哈希函数获取)
const index = hashFn(key, this.limit);
// 2、根据 index 取出对应的 bucket
let bucket = this.storage[index];
// 3、判断是否存在 bucket
if (bucket === undefined) {
bucket = []; // 不存在则创建
this.storage[index] = bucket;
}
// 4、判断是插入数据操作还是修改数据操作
for (let i = 0; i < bucket.length; i++) {
let tuple = bucket[i]; // tuple 的格式:[key, value]
if (tuple[0] === key) {
// 如果 key 相等,则修改数据
tuple[1] = value;
return; // 修改完 tuple 里数据,return 终止,不再往下执行。
}
}
// 5、bucket 新增数据
bucket.push([key, value]); // bucket 存储元组 tuple,格式为 [key, value]
this.count++;
// 判断哈希表是否要扩容,若装填因子 > 0.75,则扩容
if (this.count / this.limit > this.loadFactor) {
this.resize(this.getPrime(this.limit * 2));
}
}
// 根据 get(key) 获取 value
get(key) {
const index = hashFn(key, this.limit);
const bucket = this.storage[index];
if (bucket === undefined) {
return null;
}
for (const tuple of bucket) {
if (tuple[0] === key) {
return tuple[1];
}
}
return null;
}
// remove(key) 删除指定 key 的数据
remove(key) {
const index = hashFn(key, this.limit);
const bucket = this.storage[index];
if (bucket === undefined) {
return null;
}
// 遍历 bucket,找到对应位置的 tuple,将其删除
for (let i = 0, len = bucket.length; i < len; i++) {
const tuple = bucket[i];
if (tuple[0] === key) {
bucket.splice(i, 1); // 删除对应位置的数组项
this.count--;
// 根据装填因子的大小,判断是否要进行哈希表压缩
if (this.limit > 7 && this.count / this.limit < this.minLoadFactor) {
this.resize(this.getPrime(Math.floor(this.limit / 2)));
}
return tuple;
}
}
}
isEmpty() {
return this.count === 0;
}
size() {
return this.count;
}
// 重新调整哈希表大小,扩容或压缩
resize(newLimit) {
// 1、保存旧的 storage 数组内容
const oldStorage = this.storage;
// 2、重置所有属性
this.storage = [];
this.count = 0;
this.limit = newLimit;
// 3、遍历 oldStorage,取出所有数据,重新 put 到 this.storage
for (const bucket of oldStorage) {
if (bucket) {
for (const b of bucket) {
this.put(b[0], b[1]);
}
}
}
}
}