JS实现哈希表原理
哈希表原理
-
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
-
哈希表可以存储各种类型的数据,当我们从哈希表中查找所需要的数据时,理想情况是不经过任何比较,一次存取便能得到所查记录,那就必须在记录的存储位置和它的关键字之间建立一个确定的对应关系 f,使每个关键字和结构中一个唯一的存储位置相对应。(关键字就是所要存储的数据,存储位置相当于数组的索引)
哈希表的实现
1 这里采用链地址法实现
-
基本数据形式
[ [ ['key1', 'value1'], ['key2', 'value2'] ], [ ['key3', 'value3'] ] ] -
基本属性
size: 数组的限制长度count: 数据总个数stroge: 数组 -
基本方法
put: 添加或者修改数据get: 获取数据remove: 删除数据isEmpty: 是否为空length: 数据长度resize: 扩容数组
2 实现过程
1 准备代码
const HashTable = function(size=7) {
this.stroge = []; // 存储数据,最后数据格式应该是 [[[key,value],[key,value]]],这里采用的是数组形式
this.size = size; // 初始化数组长度,尽量取质数,容量不够时,需要扩容
this.count = 0; // loadFactor = this.count / this.size 如果超过0.75,则需要扩容
}
2 创建hash函数
- 这里采用霍纳算法取模运算,
charCodeAt获取Unicode编码 - 取模后,拿到索引值。
// 创建hash函数,这里用的是取模,根据霍纳算法取模(也可以使用JAVA中位运算 哈希化二进制 & length - 1 )
HashTable.prototype.hashFunc = function(key) {
const H = 37; // 质数
let hashCode = 0
// 采用霍纳算法求出index
for(let i = 0; i < key.length; i++) {
hashCode = H * hashCode + key.charCodeAt(i)
}
return hashCode % this.size
}
3 添加put方法
- 如果
this.stroge[index] === undefined,则需要创建this.stroge[index] = [[]] this.stroge[index] = [[key, value]],遍历,有key则更新值,无key则push添加上
// put 添加和修改是同一个
// 1. 如果this.stroge[index] === undefined ,则需要创建 this.stroge[index] = [[]]
// 2. this.stroge[index] = [[key, value]] ,遍历,有key则更新值,无key则push添加上
HashTable.prototype.put = function(key, value) {
const index = this.hashFunc(key);
// 1 判断是否存在 this.stroge[index] = [[]], 这里称为basket,有没有篮子存放对应存放key,value
if(this.stroge[index] === undefined) {
this.stroge[index] = []
}
// 遍历basket里面的数组数据
const basket = this.stroge[index]
if(basket.length) {
for(let i = 0; i < basket.length; i++) {
const tuple = basket[i]
// 若key对应,则直接修改数据,跳出
if(tuple[0] === key) {
tuple[1] = value;
return
}
}
}
// 若找不到key,则直接添加在数组后面
basket.push([key, value])
this.count++;
}
4 添加get获取方法
-
根据
hashFunc,获取出key对应的index -
找到
stroge[index],为undefined,则直接返回null,否则,获取出对应数据backet -
遍历对应的
backet,拿到value,否则返回null
// get 获取对应的value
/**
* 1 根据hashFunc,获取出key对应的index
* 2 找到stroge[index],为undefined,则直接返回null,否则,获取出对应数据backet
* 3 遍历对应的backet,拿到value,否则返回null
*/
HashTable.prototype.get = (key) => {
const index = this.hashFunc(key);
const backets = this.stroge[index]
if(backets === undefined) {
return null
}
let res = null;
for(let i = 0; i < backets.length; i++) {
if(backets[i][0] === key) {
res = backets[i][1]
break;
}
}
return res;
}
5 添加remove删除方法
-
根据
hashFunc,获取出key对应的index -
找到
stroge[index],为undefined,则直接返回null,否则,获取出对应数据backet -
遍历对应的
backet,若backet[i][0] === key,则调用splice,this.count--,否则返回null
/**
* 3 删除操作
* 根据hashFunc,获取出key对应的index
* 找到stroge[index],为undefined,则直接返回null,否则,获取出对应数据backet
* 遍历对应的backet,若backet[i][0] === key,则调用splice,否则返回null
*/
HashTable.prototype.remove = (key) => {
const index = this.hashFunc(key);
const backet = this.stroge[index];
let res = null
if(backet === undefined) {
return null
}
for(let i = 0; i < backet.length; i++) {
const tuple = backet[i];
if(tuple[0] === key) {
backet.splice(i, 1)
this.count--;
res = tuple[1]
}
}
return res
}
6 添加其他方法
isEmpty, length
/**
* isEmpty 是否为空
*/
HashTable.prototype.isEmpty = () => {
return this.count === 0
}
/**
* size 长度
*/
HashTable.prototype.length = () => {
return this.count
}
7 测试数据
const hashTable = new HashTable()
hashTable.put('abc', '123')
hashTable.put('abc', '234')
hashTable.put('abcd', '123')
hashTable.put('ab', '123')
console.log(hashTable.get('abc'))
hashTable.remove('ab')
console.log(hashTable.stroge)
console.log(hashTable.isEmpty())
console.log(hashTable.length())
3 哈希表扩容
当哈希表存在loadFactor加载因子,加载因子是表示Hsah表中元素的填满的程度.若加载因子越大,填满的元素越多,好处是,空间利用率高了,但冲突的机会加大了,反之,加载因子越小,填满的元素越少,好处是:冲突的机会减小了,但空间浪费多了。冲突的机会越大,则查找的成本越高.反之,查找的成本越小.因而,查找时间就越小.
执行添加put操作后,当loadFactor超过0.75时,则需要扩容
1 添加resize方法
当需要执行扩容后,哈希表中stroge数组中的元素需要重新存放,因为这时候的size不一样,取模后的索引也就不一样了,需要将旧的数组重新放入到新的数组中。
/**
* 扩容,当添加元素时,loadFactor(装载因子)> 0.75时,需要扩容
* 缩容,当删除元素时,loadFactor(装载因子)< 0.25 && size >= 7,需要缩容
*/
HashTable.prototype.resize = (limit) => {
const oldStore = this.stroge;
// 重置stroge,count,size
this.stroge = [];
this.count = 0;
this.size = limit;
// 将原有的数据重新添加到store中
for(let i = 0; i < oldStore.length; i++) {
const basket = oldStore[i]
if(basket === undefined) {
continue
}
for(let i = 0; i < basket.length; i++) {
const tuple = basket[i]
this.put(tuple[0], tuple[1])
}
}
}
2 添加smallerPrime方法
找出扩容2倍后距离最近的质数,保证质数是避免模数相同的数之间具备公共因数
/**
* 判断是不是质数
*/
function isPrime(num) {
// 开根号
const sqr = parseInt(Math.sqrt(num))
for(let i = 2; i <= sqr; i++) {
if(num % i === 0) {
return false
}
}
return true
}
/**
* 求出相邻最小的质数
*/
function smallerPrime(num) {
while(!isPrime(num)) {
num++
}
return num;
}
3 修改添加put方法
扩容,当添加元素时,loadFactor(装载因子)> 0.75时,需要扩容
HashTable.prototype.put = function(key, value) {
const index = this.hashFunc(key);
// 1 判断是否存在 this.stroge[index] = [[]], 这里称为basket,有没有篮子存放对应存放key,value
if(this.stroge[index] === undefined) {
this.stroge[index] = []
}
// 遍历basket里面的数组数据
const basket = this.stroge[index]
if(basket.length) {
for(let i = 0; i < basket.length; i++) {
const tuple = basket[i]
// 若key对应,则直接修改数据,跳出
if(tuple[0] === key) {
tuple[1] = value;
return
}
}
}
// 若找不到key,则直接添加在数组后面
basket.push([key, value])
this.count++;
// 当this.count / this.size >= 0.75,则需要扩容
if(this.count >= this.size * 0.75) {
// 找出最小的质数
const newSize = smallerPrime(this.size * 2)
this.resize(newSize)
}
}
4 修改remove方法
缩容,当删除元素时,loadFactor(装载因子)< 0.25 && size >= 7,需要缩容
HashTable.prototype.remove = (key) => {
const index = this.hashFunc(key);
const backet = this.stroge[index];
let res = null
if(backet === undefined) {
return null
}
for(let i = 0; i < backet.length; i++) {
const tuple = backet[i];
if(tuple[0] === key) {
backet.splice(i, 1)
this.count--;
res = tuple[1]
if(this.size > 7 && this.count < this.size * 0.25) {
const num = smallerPrime(Math.floor(this.size / 2));
this.resize(num)
}
}
}
return res
}