js实现封装哈希表

459 阅读3分钟

js实现封装哈希表

在这里插入图片描述

1.哈希表的原理

** 1.哈希表需要用数组来储存数据*

** 2.每个数组的下表是通过哈希函数来生成的*

** 3.哈希表的空间必须比要容纳的内容元素多*

** 4.一般遇到冲突使用链地址法来解决*

2.注意事项

2.1 实现高效的哈希函数

​ 哈希函数是整个哈希表的一个灵魂,通过哈希函数,可以把字段转化成hascode作为数组下标,实现哈希函数,可以使用霍纳法则,来简化多项式相乘的次数。

​ ** 为什么要使用霍纳法则?*

​ ** 对于单词转换成数字的时候,为了保证单词的唯一性,不能是把每个单词使用 unicode 转换成数字后相加,*

​ ** 因此需要对每个位置上的单词进行一定的转换*

​ ** 比如:cast 每个字母对应的unicode 分别是 99 97 115 116; 如果单纯的相加,会导致转化的数字会重复*

​ ** 因此 cast 可以这样 99*37^3 + 97*37^2 + 115*37^1 + 116*37^0 这样的数字不会重复;*

​ ** 再次问为什么要用霍纳法则呢?*

​ ** 霍纳法则可以对多项式相加进行化简,提升把字符转化成数字的速度。*

​ ** 1.如果直接使用多项式转化的话,会导致计算量非常大。大概需要 O(2^N)*

​ ** 2.霍纳法则简化后,需要时间复杂度 O(N);

​ ** 为什么hascode初始化值是0?*

​ ** 根据霍纳法则,从最高次数项开始,要保证第一个项系数都要参与运算*

3.完整代码实现

/* 哈希表 */
class HasMap {
  constructor() {
    //需要的属性
    //1.要一个储存哈希表内容的容器,
    this.storage = [];
    //2.要一个表示当前储存的数据量
    this.count = 0;
    //3.用于标记数组中一共可以存放的元素数量,这个数是用来对数组容量进行限制的。在哈希函数中起到缩小哈希化后的数字的作用,以保证哈希化后的数字能够压缩到合理的在 storage 范围内。
    this.limit = 7;
  }
  //需要一个哈希函数,把输入的数据进行哈希化,并压缩到数组范围内
  hasFunc(str) {
    //1.将字符串转换成较大的数字 hascode
    //注意!这里hascode可以是任意值,之所以写0,是因为数字比较小,计算更快。霍纳算法中,首次计算是从最高次数项开始的。所以开始的数字在一定程度可以减小后面数字的大小。
    
    let hascode = 0;
    //霍纳算法进行哈希化
    for (let i = 0; i < str.length; i++) {
      hascode = hascode * 37 + str.charCodeAt(i);
    }
    //2.把大的数字hascode压缩到数组范围之内,
    hascode = hascode % this.limit;
    return hascode;
  }
  isEmpty() {
    return !this.count;
  }
  //插入一个数据或者修改
  put(key, val) {
    //1.获取到 hascode 下标
    let hascode = this.hasFunc(key);
    //2.查询容器中该 hascode 下标位置是否有值

    //2.1 如果没有值,直接创建一个数组来储存第一个值,并返回true
    if (this.storage[hascode] == null) {
      this.storage[hascode] = [{ key, val }];
      this.count++;
      this.resize();
      return;
    }
    //2.2 如果改位置有值了,进行下一步
    //3 遍历hascode下标位置的数组,查找数组中每个对象,判断是否已经存在 key ;
    let index = 0,
      chain = this.storage[hascode];
    // console.log(chain);
    while (index < chain.length) {
      //3.1 如果存在 key 替换该位置key所对应的对象。
      if (chain[index].key == key) {
        chain[index].val = val;
        return;
      }
      index++;
    }
    //3.2 如果没有存在 key 直接在后面数组最后插入一个对象。
    chain.push({ key, val });
    this.count++;
    this.resize();
  }
  //获取数据
  get(key) {
    //1.判断是否哈希表是否为空
    //1.1如果是空,直接返回-1
    if (this.isEmpty()) return -1;
    //1.2不为空直接下一步
    //2. 获取hascode
    let hascode = this.hasFunc(key);
    //3.通过 hascode 查询该位置是否为空
    //3.1如果为空,直接返回-1
    if (!this.storage[hascode]) return -1;
    //3.2如果不为空,下一步
    //4. 循环遍历hascode位置的数组,查询key是否存在
    let index = 0,
      chain = this.storage[hascode];

    while (index < chain.length) {
      //4.1如果存在直接返回对应的val
      if (chain[index].key === key) {
        return chain[index].val;
      }
      index++;
    }
    //4.2如果不存在,返回-1
    return -1;
  }
  //删除方法
  remove(key) {
    if (this.isEmpty()) return -1;
    let hascode = this.hasFunc(key);
    if (this.storage[hascode] == null) return -1;
    let index = 0;
    while (index < this.storage[hascode].length) {
      if (this.storage[hascode][index].key == key) {
        return this.storage[hascode].splice(index, 1);
      }
      index++;
    }
    return -1;
  }
  //哈希表扩容
  resize() {
    //1.如果当前 loadFactor 装载因子超过0.75 则对数组进行扩容 ,loadFactor 是  当前容纳的数列 count与 数组限制长度 limit之比; count/limit > 0.75
    //1.1 如果 loadFactor 小于0.75,直接return;
    if (this.count / this.limit < 0.75) return;
    //1.2 如果大于 0.75 下一步
    //2. 对数组容纳量扩充约等于2倍,但是要求必须是质数倍
    this.limit = this.limit * 2 + 1;
    while (!this.isPrime(this.limit)) {
      this.limit++;
    }
    //3. 保存当前 storage 并把原来的 storage ,count 清空 //这里要重新初始化,并且重新插入所有数据,是因为 数组限制长度(limit)改变后,原来通过哈希函数获取的hascode已经不能在新的数组上对应了。因此要重新创建并插入;
    let oldStorage = this.storage;
    this.storage = [];
    this.count = 0;
    //4.遍历旧的storage 并重新插入新的 storgae中。
    let index = 0;
    while (index < oldStorage.length) {
      if (oldStorage[index]) {
        let chainIndex = 0;
        while (chainIndex < oldStorage[index].length) {
          let obj = oldStorage[index][chainIndex];
          this.put(obj.key, obj.val);
          chainIndex++;
        }
      }
      index++;
    }
    console.log('重新插入成功,当前limit是:%d',this.limit);
  }
  //判断一个数是不是质数
  isPrime(num) {
    //1. 对这个数进行二分,得到一个较小的数.
    let newNum = parseInt( Math.sqrt(num) );
    //2. 循环遍历这个数,判断用输入的数 % 遍历的每个数
    for (let i = 2; i <= newNum; i++) {
      //2.1 如果取模后等于0,说明不是质数,返回fasle
      if (num % i == 0) {
        return false;
      }
    }
    //2.2 如果遍历完成没有发现等于0的,则返回true;
    return true;
  }
}
let hastable = new HasMap();
hastable.put("lipeng11", { age: "18", sex: "男" });
hastable.put("chen", { age: "3", sex: "女" });
console.log(hastable.remove("lipeng11"));
console.log(hastable.get("lipeng11"));

个人博客地址:点击进入我的个人博客