1.哈希函数
好的哈希函数应该尽可能的让计算的过程变得简单,提高计算的效率
- 哈希表的主要优点就是他的速度
- 提高速度的办法就是尽量少用乘法和除法,因为其性能较低。
那么如何设计?
- 哈希表的优势在于效率,所以快速获取到对应的hashCode非常重要
- 我们需要快速的计算来获取到元素的对应的hashCode
- 让元素再哈希表中均匀分布
2.使用秦九韶算法实现快速计算。
- 例如cats = 327^3 + 127^2 + 20*27 + 17
- 使用秦九韶算法 ( (3*27+1)*27^2 + 20)*27 + 17
- 计算再变换后我们需要多少次乘法,多少次加法
- 时间复杂度从O(N^2) -> O(N)
3.为了实现均匀分布,最好将常量使用为质数
- 哈希表长度使用质数
- 幂的底数使用质数
4.哈希函数的实现
//设计哈希函数
// 1> 将字符串转换为比较大的数字 hashCode
// 2> 将大的数字压缩到某个数组范围(大小)内
// 字符串 str 数组范围,数组大小size
function hashFunc(str, size){
//1.定义hashCode变量
var hashCode = 0;
//2.秦九韶算法,来计算hashCode的值
//abc为例
for(var i = 0; i < str.length; i++){
hashCode = 37 * hashCode + str.charCodeAt(i);
//相当于秦九韶算法每次都乘以37
//37 * 0 + 97
//37*97 + 98
//37 *(37*97 + 98)+ 99
}
//3.取余操作
index = hashCode % size;
return index;
}
// 测试哈希函数
alert(hashFunc('abc', 7)); //4
alert(hashFunc('cba', 7)); //3
alert(hashFunc('nba', 7)); //5
alert(hashFunc('mba', 7)); //1
// alert(hashFunc('abc', 7));
5.创建哈希表
- 我们这里采用链地址法实现哈希表,每个index对应一个数组,防染链表也行
- 数组里面我们继续放一个数组[key,value]的形式
- 最后我们创建的哈希表数据形式是:[[k,v],[k,v],[k,v],[k,v]]
- 定义三个属性:
- storage 作为我们的数组,存放相关的元素
- count 表示当前已经存放了多少数据
- limit 用于标记数组中一共可以放多少个元素
/封装哈希表的类
function hashTable(){
// 属性
this.storage = [];
this.count = 0;
//用来计算装载因子,大于0.75我们要进行扩容,小于0.25要将数组变小
this.limit = 7;
//哈希表里面的数组的总长度
// 方法
}
6.哈希表相关的操作方法
插入和修改元素
- 哈希表的插入和修改方法是一个方法
- 当传入的<key,value>时不存在这个key时就是插入操作,存在这个key就是修改
- 第一件事情:根据我们传入的key获取索引值,将我们的数据插入到对应的位置
- 第二件事情:根据索引值取出bucket
- 如果不存在bucket,那么就要创建bucket,并且放在该索引的位置
- 判断我们是新增还是修改原来的值
- 如果已经有值了那么就修改
- 如果没有就执行后续的添加操作
//插入和修改方法
HashTable.prototype.put = function (key, value) {
//1.首先根据哈希函数获取到index
var index = this.hashFunc(key, this.limit);
//2.根据index找到bucket
var bucket = this.storage[index];
//3.判断buckjet是否存在,不存在的话新建一个bucket并且将当前的bucket添加进数组
if (bucket == null) {
bucket = [];
this.storage.push(bucket);
}
//4.如果有值了就修改
for (var i = 0; i < bucket.length; i++) {
var tuple = bucket[i];
if (tuple[0] == key) {
tuple[1] = value;
return "修改成功";
//修改的话就不用再继续执行下去了。
}
}
//5.执行到这一步说明之前不是修改数据而是新增数据。
bucket.push([key, value]);
this.count += 1;
}
获取操作
- 第一步是根据key获取对应的index
- 第二部根据index获取对应的bucket
- 第三步判断bucket是否为null,如果为null直接返回null
- 第四步线性查找bucket中锋的每一个key是否等于传入的key,如果等于直接返回对应的value
//获取操作
//插入和修改方法
HashTable.prototype.put = function (key, value) {
//1.首先根据哈希函数获取到index
var index = this.hashFunc(key, this.limit);
//2.根据index找到bucket
var bucket = this.storage[index];
//3.判断buckjet是否存在,不存在的话新建一个bucket并且将当前的bucket添加进数组
if (bucket == null) {
bucket = [];
this.storage.push(bucket);
}
//4.如果有值了就修改
for (var i = 0; i < bucket.length; i++) {
var tuple = bucket[i];
if (tuple[0] == key) {
tuple[1] = value;
return "修改成功";
//修改的话就不用再继续执行下去了。
}
}
//5.执行到这一步说明之前不是修改数据而是新增数据。
bucket.push([key, value]);
this.count += 1;
}
删除操作
- 根据我们的key获取对应的index
- 根据index来获取我们的bucket
- 判断这个bucket是否存在,如果不存在,那么直接返回false,或者把删除的东西返回,null
- 线性查找bucket,寻找相应的数据,并且删除
- 依然没有找到,那么返回null
//删除操作
//删除操作
HashTable.prototype.remove = function(key){
//1.根据key来寻找index
var index = this.hashFunc(key,this.limit);
//2.根据index寻找对应的bucket
var bucket = this.storage[index];
//3.判断桶是否存在
if(bucket == null){
return "删除失败";
}
//4.线性查找bucket,并且删除
for(var i = 0 ; i < bucket.length; i ++){
var tuple = bucket[i];
if(tuple[0] == key){
bucket.splice(i,1);
this.count -= 1;
return tuple[1];
}
}
//5.遍历完依旧没有找到
return "删除失败"
}
完整操作以及代码测试
//封装哈希表类
function HashTable() {
// 属性
this.storage = [];
this.count = 0;
//用来计算装载因子,大于0.75我们要进行扩容,小于0.25要将数组变小
this.limit = 7;
//哈希表的长度
//1.设计哈希函数
HashTable.prototype.hashFunc = function (str, size) {
var hashCode = 0;
for (var i = 0; i < str.length; i++) {
hashCode = 37 * hashCode + str.charCodeAt(i);
}
index = hashCode % size;
return index;
}
//方法
//插入和修改方法
HashTable.prototype.put = function (key, value) {
//1.首先根据哈希函数获取到index
var index = this.hashFunc(key, this.limit);
//2.根据index找到bucket
var bucket = this.storage[index];
//3.判断buckjet是否存在,不存在的话新建一个bucket并且将当前的bucket添加进数组
if (bucket == null) {
bucket = [];
this.storage.push(bucket);
}
//4.如果有值了就修改
for (var i = 0; i < bucket.length; i++) {
var tuple = bucket[i];
if (tuple[0] == key) {
tuple[1] = value;
return "修改成功";
//修改的话就不用再继续执行下去了。
}
}
//5.执行到这一步说明之前不是修改数据而是新增数据。
bucket.push([key, value]);
this.count += 1;
}
//获取操作
HashTable.prototype.get = function (key) {
//1.根据key来找到index
var index = hashFunc(key, this.limit);
//2.根据index寻找对应的bucket
var bucket = this.storage[index];
//3.判断bucket是否为空
if (bucket == null) {
return null;
}
//4.如果有bucket的话,就线性查找
for (var i = 0; i < bucket.length; i++) {
var tuple = bucket[i];
if (tuple[0] == key) {
return tuple[1];
}
}
//5.如果线性查找不到就为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];
//3.判断桶是否存在
if (bucket == null) {
return "删除失败";
}
//4.线性查找bucket,并且删除
for (var i = 0; i < bucket.length; i++) {
var tuple = bucket[i];
if (tuple[0] == key) {
bucket.splice(i, 1);
this.count -= 1;
return tuple[1];
}
}
//5.遍历完依旧没有找到
return "删除失败"
}
//其他方法
//判断哈希表是否为null
HashTable.prototype.isEmpty = function () {
return this.count == 0;
}
//返回哈希表的长度
HashTable.prototype.size = function () {
return this.count;
}
}
var ht = new HashTable();
console.log("新增数据————————————————————————")
ht.put('1', 'aaa');
ht.put('2', 'bbb');
ht.put('3', 'ccc');
ht.put('4', 'ddd');
console.log(ht);
console.log("修改数据————————————————————————")
ht.put('1', 'nba');
ht.put('2', 'cba');
console.log(ht);
console.log("删除数据————————————————————————")
ht.remove('1');
ht.remove('5');
console.log(ht);
</script>
测试结果: