参考
- b站视频链接:www.bilibili.com/video/BV1x7…
- 参考的github笔记:github.com/XPoet/JS-Da…
哈希表(HashTable)
-
数组和链表优缺点
- 因为原来数组的增加、修改、删除元素和根据内容查找元素的操作效率低,但根据下标查找元素的效率高
- 链表的查找需要遍历链表,效率较低,而其它操作效率较高
-
哈希表优缺点
- 查找、插入、删除效率高,但空间利用率低
- 但不能快速找出哈希表中的最大、最小这些值
-
什么是哈希表?
- 通过将关键字转换成相应的数组下标,就可以直接根据此下标来进行增、删、查、改
- 哈希表元素是无序的,key是不重复的,但不同的key通过哈希函数产生的HashCode有可能相同,因此需要解决此类问题(冲突)
-
解决冲突的方法
- 链地址法(拉链法)
- 数组中每个元素存储数组或者链表
- 开放地址法
- 按照线性探测、二次探测、再哈希法(步长不同),去寻找数组中的空白位置进行存储有冲突的元素
- 线性探测:连续位置填充数据,聚集现象导致探测步长会很长
- 二次探测:能防止出现连续位置填充数据的现象
- 再哈希法:stepSize = const - (HashCode % const)
- 按照线性探测、二次探测、再哈希法(步长不同),去寻找数组中的空白位置进行存储有冲突的元素
- 链地址法(拉链法)
-
优秀的哈希函数
- 尽量少用乘法、除法
- 如:多项式用秦九韶算法来优化
- 尽可能让数据均匀分步(即常量尽量用质数)
- 如:容量用质数
- 尽量少用乘法、除法
// 基于数组来模拟链地址法 来实现哈希表
/*
[
[[key1,value1]],
[[key2,value2]]...
]
*/
function HashTable(){
this.storage = [];
this.count = 0;// 已存放元素个数
this.limit = 7// 最多可存储的元素个数
// resize(newLimit)
// loadFactor:加载因子 > 0.75进行扩容,< 0.25 进行缩小
HashTable.prototype.resize = function(newLimit){
// 1、记录旧哈希表
var oldStorage = this.storage;
// 2、新哈希表初始化
this.count = 0;
this.storage = [];
this.limit = newLimit;
// 3、将旧哈希表重新插入新的哈希表
var len = oldStorage.length;
for(var i = 0; i < len; i ++){
var buckey = oldStorage[i];
if(!buckey){
// 桶空
continue;
}else{
// 桶非空,遍历旧桶的元素,将其插入到新哈希表中
var b_len = buckey.length;
for(var j = 0; j < b_len; j ++){
var tuple = buckey[j];
this.insert(tuple[0],tuple[1]);
}
}
return this.storage;
}
}
// hashFunc(key,limit):根据key获取相应HashCode,0 ~ limit - 1
HashTable.prototype.hashFunc = function(key,limit){
var hashCode = 0;
// 霍纳算法
var len = key.length;
for(var i = 0; i < len; i ++){
hashCode = 37 * hashCode + key.charCodeAt(i);
}
var index = hashCode % limit;
return index;
}
// insert(key,value)
/*
1、已存放key,修改value
2、未存放key,插入[key,value]
*/
HashTable.prototype.insert = function(key,value){
// 1、获取HashCode
var index = this.hashFunc(key,this.limit);
var buckey = this.storage[index];
// 2、判断桶是否为空
if(!buckey){
// 桶空,创建桶
buckey = [];
this.storage[index] = buckey;
}
// 3、遍历桶的元素,已有key,修改value,无则添加
var len = buckey.length;
for(var i = 0; i < len; i ++){
var tuple = buckey[i];
if(key === tuple[0]){
tuple[1] = value;
return true;
}
}
buckey.push([key,value]);
this.count ++;
// 4、判断当前数组是否需要扩容
if(this.count > this.limit * 0.75){
// 获取质数后扩容
var loadFactor = getPrime(this.limit * 2);
this.resize(loadFactor);
}
return true;
}
// get(key):根据key获取value
HashTable.prototype.insert = function(key){
// 1、获取HashCode
var index = this.hashFunc(key);
// 2、判断桶是否为空
var buckey = this.storage[index];
if(!buckey){
// 桶空
return null;
}else{
// 桶非空,遍历桶的元素找到相应key
var len = buckey.length;
for(var i = 0; i < len; i ++){
var tuple = buckey[i];
if(key === tuple[0]){
return tuple[1];
}
}
}
return null;
}
// remove(key):根据key删除[key,value]
HashTable.prototype.remove = function(key){
// 1、根据key获取HashCode
var index = this.hashFunc(key);
// 2、判断桶是否为null
var buckey = this.storage[index];
if(!buckey){
// 桶null
return false;
}else{
// 桶非null,遍历桶的元素
var len = buckey.length;
for(var i = 0; i < len; i ++){
var tuple = buckey[i];
if(key === tuple[0]){
buckey.splice(i,1);
this.count --;
// 3、判断是否缩容,最小是7
if(this.limit > 7 && this.count < this.limit * 0.25){
var loadFactor = this.getPrime(Math.floor(this.limit / 2));
this.resize(loadFactor);
}
return tuple[1];
}
}
}
return false;
}
// isEmpty()
HashTable.prototype.isEmpty = function(){
return this.count === 0;
}
// size()
HashTable.prototype.size = function(){
return this.count;
}
// isPrime(num)
HashTable.prototype.isPrime = function(num){
if(num === 1)
return false;
var sqrt_num = parseInt(Math.sqrt(num));
for(var i = 2; i <= sqrt_num; i ++){
if(num % i === 0)
return false;
}
return true;
}
HashTable.prototype.getPrime = function(num){
while (!this.isPrime(num)){
num++;
}
return num;
};
}
// 测试代码
var hashTable = new HashTable();
console.log(hashTable.insert("ac", 97));
console.log(hashTable.insert("b", 97));
console.log(hashTable.insert("a", 64));
console.log(hashTable.get("a"));
console.log(hashTable.remove("a"));
// 测试扩容
hashTable.insert("afs", 65);
hashTable.insert("akl", 23);
hashTable.insert("amk", 49);
hashTable.insert("fd", 46);