目录
一、哈希表相关操作方法的前提
如果还不了解什么是哈希表,可以去看博文:认识哈希表和实现哈希函数。这里介绍的是链地址法。
二、哈希表的封装
JS数据结构中,HashTable的封装,定义三个属性:
- this.storage 哈希表的实质是数组,用数组来存放数据。
- this.count 记录当前哈希表中已经存放了多少数据。
- this.limit 数组总长,记录数组中一共可以存放多少个元素,为质数。
function HashTable(){
// 属性
this.storage = [];
this.count = 0;
this.limit = 7;
}
在实现各种操作之前,首先需要使用原型将我们封装好的哈希函数引入进来。
// 哈希函数
HashTable.prototype.hashFunc = function (str, size) {
var hashCode = 0;
for (var i = 0; i < str.length; i++) {
hashCode = 37 * hashCode + str.charCodeAt();
}
var index = hashCode % size;
return index;
}
三、效率提升
注意:
count 与 limit 的比值成为装填因子(总数据项 / 哈希表长度)。
- 在开放地址法中,装填因子最大是1,因为它必须查找到空白的位置才能够将元素放进去,而链地址法的装填因子可能大于1,你可以无限的插入元素,但是当装填因子过大时,也就是随着数据量的增多,每一个index对应的桶bucket会越来越长,我们进行插入查找和删除操作的效率都会变得非常低,这时候就需要扩容。
- 当然当进行删除操作时,我们的数据变得越来越少,这时候的count变小,而装填因子变得越来越小,也会造成效率变低,这时候哦就需要缩容。
如何扩容和缩容?
在需要扩容的时候,将数组的长度扩大到两倍,但是前面我们提到,limit 是数组总长,记录数组中一共可以存放多少个元素,为质数。因此我们需要一个算法来得到扩容后的数组总长,涉及到一个判断并寻找质数。扩容可以将原有的数组长度*2,再一次加1寻找质数。缩容可以将原有的数组长度 / 2,再一次加1寻找质数。
算法如下:
1、判断是否为质数
// 判断是否为质数 HashTable.prototype.isPrime = function (num) { var temp = parseInt(Math.sqrt(num)); for (var i = 2; i <= temp; i++) { if (num % i == 0) return false; } return true; }
2、获取质数
// 获取质数的函数 HashTable.prototype.getPrime = function (num) { while(!this.isPrime(num)){ num++; } return num; }
3、重新存储
// 扩容和缩容的重新存储 HashTable.prototype.resize = function (newLimit) { // 1、将旧的数组内容保存到oldStorage中 var oldStorage = this.storage; // 2、重置所有的属性 this.storage = []; this.count = 0; this.limit = 7; // 3、遍历oldStorage中的所有的桶bucket for (var i = 0; i < oldStorage.length; i++) { // 3.1取出对应的bucket var bucket = oldStorage[i]; // 3.2判断bucket是否为空 if (bucket == null) continue; // 3.3 bucket不为空,则将元素重新插入到新的数组中 for (var j = 0; j < bucket.length; j++) { var tuple = bucket[j]; this.put(tuple[0], tuple[1]); } } }
何时需要扩容和缩容?
当总数据项 / 哈希表长度 = count / limit,即装填因子大于0.75的时候需要扩容。
当哈希表长度大于7,且装填因子小于0.25的时候需要缩容。
四、方法实现。
1、向哈希表添加元素put(key, value)。
思路: 1、根据key获取index值,找到相应的位置。
2、根据对应的index位置,获取对应的bucket。
- 1)如果桶存在就直接插入或修改。
- 2)如果桶不存在则创建桶。
3、判断是该桶中是否存在该key。
- 1)如果存在则修改元素的value值。
- 2)如果不存在则遍历到最后将之插入。
代码实现:
HashTable.prototype.put = function (key, value) {
// 1、根据key获取index值,找到相应的位置
var index = this.hashFunc(key, this.limit);
// 2、根据对应的index位置,获取对应的bucket
var bucket = this.storage[index];
// 如果桶不存在则创建桶
if (bucket == null) {
bucket = [];
this.storage[index] = bucket;
}
// 3、判断是该桶中是否存在该key
// 1)如果存在则修改元素的value值
for (var i = 0; i < bucket.length; i++) {
var tuple = bucket[i];
if (tuple[0] == key) {
tuple[1] = value;
return ;
}
}
// 2)如果不存在则遍历到最后将之插入
bucket.push([key, value]);
this.count += 1;
// 4、判断是否需要扩容
if (this.count > this.limit * 0.75) {
var newSize = this.limit * 2;
var newPrime = this.getPrime(newSize);
this.resize(newPrime);
}
}
2、获取某个元素的值get(key)。
思路:
1、根据key获取index值,找到相应的位置。
2、根据对应的index位置,获取对应的桶bucket。
- 如果桶bucket不存在则返回null。
- 如果桶bucket存在,则遍历查找是否存在该key值。
- 存在则返回value值。
- 不存在返回null。
代码实现:
// 获取操作
HashTable.prototype.get = function (key) {
// 1、根据key获取index值,找到相应的位置
var index = this.hashFunc(key, this.limit);
// 2、根据对应的index位置,获取对应的bucket
var bucket = this.storage[index];
// 如果桶不存在则返回null
if (bucket == null) return null;
// 如果桶存在,则遍历查找是否存在该key值,存在则返回value值
for (var i = 0; i < bucket.length; i++) {
var tuple = bucket[i];
if (tuple[0] == key) {
return tuple[1];
}
}
// 不存在返回null
return null;
}
3、删除操作remove(key)
该操作与get方法很相似。
思路:
1、根据key获取index值,找到相应的位置
2、根据对应的index位置,获取对应的bucket
- 如果桶bucket不存在则返回null
- 如果桶bucket存在,则遍历查找是否存在该key值。
- 存在进行删除操作,并返回其值
- 不存在返回null
代码实现:
// 删除操作
HashTable.prototype.remove = function (key) {
// 1、根据key获取index值,找到相应的位置
var index = this.hashFunc(key, this.limit);
// 2、根据对应的index位置,获取对应的bucket
var bucket = this.storage[index];
// 如果桶不存在则返回null
if (bucket == null) return null;
// 如果桶存在,则遍历查找是否存在该key值,存在进行删除操作,并返回其值
for (var i = 0; i < bucket.length; i++) {
var tuple = bucket[i];
if (tuple[0] == key) {
bucket.splice(i, 1);
this.count -= 1;
// 3、判断是否需要缩容
if (this.limit > 7 && this.count < this.limit * 0.25) {
var newSize = Math.floor(this.limit / 2);
var newPrime = this.getPrime(newSize);
this.resize(newPrime);
}
return tuple[1];
}
}
// 不存在返回null
return null;
}
4、其他方法
// 判断哈希表是否为空
HashTable.prototype.isEmpty = function () {
return this.count == 0;
}
// 返回哈希表的长度
HashTable.prototype.size = function () {
return this.count;
}
5、代码测试
// 测试代码
// 1、创建哈希表
var ht = new HashTable();
// 2、插入数据
ht.put('abc', '123');
ht.put('cba', '321');
ht.put('hjg', '564');
ht.put('kuo', '854');
// 3、获取数据
alert(ht.get('abc')); //123
// 4、修改操作
ht.put('abc', '222');
alert(ht.get('abc')); //222
// 5、删除操作
ht.remove('abc');
alert(ht.get('abc')); //null
五、完整代码
<!DOCTYPE html>
<html>
<head>
<title>哈希表相关的函数</title>
</head>
<body>
<script type="text/javascript">
function HashTable(){
// 属性
this.storage = [];
this.count = 0;
this.limit = 7;
// 哈希函数
HashTable.prototype.hashFunc = function (str, size) {
var hashCode = 0;
for (var i = 0; i < str.length; i++) {
hashCode = 37 * hashCode + str.charCodeAt();
}
var index = hashCode % size;
return index;
}
// 方法
// 添加操作
HashTable.prototype.put = function (key, value) {
// 1、根据key获取index值,找到相应的位置
// 2、根据对应的index位置,获取对应的bucket
// 1)如果桶存在就直接插入或修改
// 2)如果桶不存在则创建桶
// 3、判断是该桶中是否存在该key
// 1)如果存在则修改元素的value值
// 2)如果不存在则遍历到最后将之插入
// 1、根据key获取index值,找到相应的位置
var index = this.hashFunc(key, this.limit);
// 2、根据对应的index位置,获取对应的bucket
var bucket = this.storage[index];
// 如果桶不存在则创建桶
if (bucket == null) {
bucket = [];
this.storage[index] = bucket;
}
// 3、判断是该桶中是否存在该key
// 1)如果存在则修改元素的value值
for (var i = 0; i < bucket.length; i++) {
var tuple = bucket[i];
if (tuple[0] == key) {
tuple[1] = value;
return ;
}
}
// 2)如果不存在则遍历到最后将之插入
bucket.push([key, value]);
this.count += 1;
// 4、判断是否需要扩容
if (this.count > this.limit * 0.75) {
var newSize = this.limit * 2;
var newPrime = this.getPrime(newSize);
this.resize(newPrime);
}
}
// 获取操作
HashTable.prototype.get = function (key) {
// 1、根据key获取index值,找到相应的位置
var index = this.hashFunc(key, this.limit);
// 2、根据对应的index位置,获取对应的bucket
var bucket = this.storage[index];
// 如果桶不存在则返回null
if (bucket == null) return null;
// 如果桶存在,则遍历查找是否存在该key值,存在则返回value值
for (var i = 0; i < bucket.length; i++) {
var tuple = bucket[i];
if (tuple[0] == key) {
return tuple[1];
}
}
// 不存在返回null
return null;
}
// 删除操作
HashTable.prototype.remove = function (key) {
// 1、根据key获取index值,找到相应的位置
var index = this.hashFunc(key, this.limit);
// 2、根据对应的index位置,获取对应的bucket
var bucket = this.storage[index];
// 如果桶不存在则返回null
if (bucket == null) return null;
// 如果桶存在,则遍历查找是否存在该key值,存在进行删除操作,并返回其值
for (var i = 0; i < bucket.length; i++) {
var tuple = bucket[i];
if (tuple[0] == key) {
bucket.splice(i, 1);
this.count -= 1;
// 3、判断是否需要缩容
if (this.limit > 7 && this.count < this.limit * 0.25) {
var newSize = Math.floor(this.limit / 2);
var newPrime = this.getPrime(newSize);
this.resize(newPrime);
}
return tuple[1];
}
}
// 不存在返回null
return null;
}
// 其他方法
// 判断哈希表是否为空
HashTable.prototype.isEmpty = function () {
return this.count == 0;
}
// 返回哈希表的长度
HashTable.prototype.size = function () {
return this.count;
}
// 判断是否为质数
HashTable.prototype.isPrime = function (num) {
var temp = parseInt(Math.sqrt(num));
for (var i = 2; i <= temp; i++) {
if (num % i == 0) return false;
}
return true;
}
// 获取质数的函数
HashTable.prototype.getPrime = function (num) {
while(!this.isPrime(num)){
num++;
}
return num;
}
// 扩容和缩容的重新存储
HashTable.prototype.resize = function (newLimit) {
// 1、将旧的数组内容保存到oldStorage中
var oldStorage = this.storage;
// 2、重置所有的属性
this.storage = [];
this.count = 0;
this.limit = 7;
// 3、遍历oldStorage中的所有的桶bucket
for (var i = 0; i < oldStorage.length; i++) {
// 3.1取出对应的bucket
var bucket = oldStorage[i];
// 3.2判断bucket是否为空
if (bucket == null) continue;
// 3.3 bucket不为空,则将元素重新插入到新的数组中
for (var j = 0; j < bucket.length; j++) {
var tuple = bucket[j];
this.put(tuple[0], tuple[1]);
}
}
}
}
// 测试代码
// 1、创建哈希表
var ht = new HashTable();
alert(ht.isPrime(11)); //true
alert(ht.isPrime(123)); //false
alert(ht.isPrime(17)); //true
alert(ht.isPrime(9)); //false
// 2、插入数据
ht.put('abc', '123');
ht.put('cba', '321');
ht.put('hjg', '564');
ht.put('kuo', '854');
// 3、获取数据
alert(ht.get('abc')); //123
// 4、修改操作
ht.put('abc', '222');
alert(ht.get('abc')); //222
// 5、删除操作
ht.remove('abc');
alert(ht.get('abc')); //null
</script>
</body>
</html>