实现哈希表

一:什么是哈希表?

哈希表是一种常见的数据结构,它通过将键映射到值的方式来存储和组织数据。具体来说,哈希表使用哈希函数将每个键映射到一个索引(在数组中的位置),然后将该键值对存储在该索引处。当需要检索某个键的值时,哈希表会再次使用哈希函数将该键转换为相应的索引,并在该索引处查找其关联的值。由于哈希函数通常能够较快地计算出索引,因此哈希表在插入和查找操作上具有很高的效率。

在 JavaScript 中,哈希表通常是通过对象来实现的。对象的属性名就是键,而属性值则是与之关联的值。然而,在某些情况下,如果需要更多的控制权或者需要处理大量的数据,可以使用第三方库或自己实现哈希表数据结构。

二:哈希化

哈希化是将任意长度的消息(例如一个字符串、文件或数据记录)转换为固定长度的值(通常是较小的整数),该值称为哈希值。哈希函数执行哈希化操作,它将输入键映射到唯一的索引值,这个索引值可以用来在哈希表中查找对应的值。

哈希化具有以下特点:

  1. 哈希化是单向的:从哈希值无法推断出原始输入值,而且很难找到两个不同的输入值生成相同的哈希值。
  2. 哈希化是确定性的:对于给定的输入,哈希函数总是返回相同的哈希值。这个特性使得哈希表能够高效地进行查找和插入等操作。
  3. 哈希化是均匀的:好的哈希函数应该将输入数据均匀地分布在哈希空间中,以避免碰撞(即两个不同的输入值生成相同的哈希值)。

总而言之

哈希化:将大数字转化成数组范围内下标的过程,我们就称之为哈希化

哈希函数:通常我们会将单词转成大数字,大数字在进行哈希化的代码实现放在一个函数中,这个函数我们称为哈希函数

哈希表:最终将数据插入到的这个数组,对整个结构的封装,我们就称之为是一个哈希表

三:哈希函数

好的哈希函数应该具备:

快速的计算:哈希表的优势就在于效率,所以快速获取到对应的hashCode非常重要。我们需要通过快速的计算来获取到元素对应的hashCode。

均匀的分布:哈希表中,无论是链地址法还是开放地址法,当多个元素映射到同一个位置的时候,都会影响效率,所以,优秀的哈希函数应该尽可能将元素映射到不同的位置,让元素在哈希表中均匀的分布。

哈希函数设计

  • 将字符串转换成比较大的数字:hashCode
  • 将大的数字hashCode压缩到数组范围之内
  • 传入两个参数:str 字符串;size 数组大小
 function hashFunc(str, size) { //参数1 字符串 参数2 数组大小
        //1.定义hashcode
        let hashCode = 0;

        //2.霍纳法则,来计算hashCode的值
        // 将str转成Unicode编码
        for (let i = 0; i <str.length ; i++) {
            //  哈希表的长度使用质数 43 47 47 31 等等都可,目前最多使用的还是37
            hashCode = 37*hashCode + str.charCodeAt(i)
        }
        //3.取余操作
        let index = hashCode % size
        return index
    }

四:实现哈希表(链地址法)

1.逻辑

实现的哈希表(基于storage的数组)每个index对应的是一个是数组(bucket)。
bucket中存放什么呢?我们最好将key和value都放进去,我们继续使用一个数组
最终我们的哈希表的数据格式是这样:[[[k,v],[k,v],[k,v]],[[k,v],[k,v]],[[k,v]]]

2.扩容和扩容时机

将所有的数据项放在长度为7的数组中的,因为我们使用的是链地址法,loadFactor可以大于1,所以这个哈希表可以无限制的插入新数据。但是随着数据量的增多,每一个index对应的bucket会越来越长,也就造成效率的降低。所以,需要在合适的情况对数组进行扩容。比如扩容两倍。

扩容时机

比较常见的情况是填装因子 > 0.75 的时候扩容

3.属性和方法

定义属性:

storage: 作为我们的数组,数组中存放相关的元素;
count:表示当前数组中已经存放了多少数据,用于计算填装因子的值,以便扩容等操作;
limit:用于标记数组的长度;

定义方法:

  • put(key,value):新增和修改方法。哈希表的插入和修改是同一个函数,因为,当使用者传入一个<key,value>时,如果原来不存在该key,那么就是插入操作;如果已经存在该key,那么就是修改操作
    • 根据传入的key获取对应的hashCode,也就是数组的index;
  • 从哈希表的index位置中取出bucket(一个数组);

  • 查看上一步取出的数组是否为null ,如果为null,则表示之前在该位置没有放置过任何的内容,那么就新建一个数组[];

  • 查看是否之前已经放置过key对应的value:如果放置过,那么就是依次替换操作,而不是插入新数据;
    如果不是修改操作,那么插入新的数据 :在数组中push新的[key,value]即可。注意: 这里需要将count+1,因为数据增加了一项。

  • 最后判断是否需要扩容: loadFactor > 0.75 的时候扩容

    是则加一再判断,直到是质数,则跳出返回这个质数

  • get(key):获取数据方法
    • 根据传入的key获取对应的hashCode,也就是数组的index;
    • 根据index拿到对应的bucket;
    • 判断bucket是否为null,如果是null则返回null;
    • 如果不是null,则遍历bucket,判断里面是否包含key对应的数据,有则返回;
    • 如果bucket中没有找到,那么返回null;
  • remove(key):删除方法
    • 根据传入的key获取对应的hashCode,也就是数组的index;
    • 根据index拿到对应的bucket;
    • 判断bucket是否存在,如果不存在则返回null;
    • 如果存在则遍历bucket,找到里面包含key对应的数据,有则删除,并且数量减一,同时需要判断是否需要缩容,返回被删除的数据;
    • 如果不存在则返回null
  • isEmpty():判断是否为空
    • 返回 count是否等于0
  • size():获取哈希表大小
    • 返回count值
  • resize(newLimit):扩容和缩容方法
    • 先存储原数组
    • 原属性重置
    • 遍历原数组,把里面每个索引中的每个bucket中的每一个值都取出来重新添加进扩容后的数组中
  • isPrime(num):判断是否为质数
    • 将传入的num开平方根,然后遍历2和开平方根之后的值之间的数值,如果传入的num值可以和这中间一个值除尽,那么就跳出返回false不是质数。遍历结束返回true,是质数。
  • getPrime(num):获取质数size
    • 循环遍历判断传入的值是否为质数,如果不

4.代码

 //哈希表
    function HashTable ()
    {
      //属性
      //哈希表的基准数组
      this.storage = []
      //哈希表中的元素总数,[key,val]
      this.count = 0
      //基准数组的大小
      this.limit = 7

      //哈希函数: 将str字符串转换成大的数字hashcode,再将大的数字压缩到数组大小(limit)范围以内index
      HashTable.prototype.hashFunc = function (str, size)
      {
        //1.定义hashcode
        let hashCode = 0

        //2.霍纳法则来计算hashcode
        //将str转换成Unicode编码
        for (let i = 0; i < str.length; i++) {
          //哈希表的长度使用质数43 47 等等,
          hashCode = 37 * hashCode + str.charCodeAt(i)
        }

        //3.取余操作
        let index = hashCode % size
        return index
      }

      //put方法,添加&修改:如果storage中原先不存在key即插入添加操作,如果存在即修改操作
      HashTable.prototype.put = function (key, value)
      {
        //1.根据key调用哈希函数来计算index值
        let index = this.hashFunc(key, this.limit)
        //2.根据索引值index获取在storage中对应的bucket数组
        let bucket = this.storage[index]
        //3.判断bucket是否为null,如果是null,那就创建[]
        if (bucket === null || bucket === undefined) {
          bucket = []
          //并且storage[index]创建[]
          this.storage[index] = bucket
        }
        //4.bucket不为空,遍历bucket,找到对应key,如果存在key,那就修改,不存在即添加插入
        //拉链法中的元素存储结构为,[[[k,v],[k2,v2],[k3,v3]]],即bucket数组,[[k,v],[k2,v2],[k3,v3]]
        for (let i = 0; i < bucket.length; i++) {
          let tuple = bucket[i]
          if (tuple[0] === key) {
            tuple[1] = value
            return
          }
        }
        //5.如果没有跳出,那就说明没有找到对应的key,那就添加
        bucket.push([key, value])
        //6.计数器this.count++
        this.count++
        //7.判断是否需要扩容 loadFactor(填装因子) > 0.75时候扩容
        if (this.count > this.limit * 0.75) {
          //扩容后新的数组大小为原来的两倍
          const newSize = this.limit * 2
          //getPrime获取质数方法,目的为了使哈希表的长度为质数
          const primeSize = this.getPrime(newSize)
          //调用resize扩容或缩容的方法
          this.resize(primeSize)
        }
      }
      //get方法:通过key来获取哈希table中的value值
      HashTable.prototype.get = function (key)
      {
        //1.通过key来调用哈希函数获取index
        let index = this.hashFunc(key, this.limit)
        //2.通过index来获取到对应的bucket
        let bucket = this.storage[index]
        //3.判断bucket是否为空
        if (bucket === null || bucket === undefined) {
          return null
        }
        //4.如果存在遍历获取
        for (let i = 0; i < bucket.length; i++) {
          let tuple = bucket[i]
          if (tuple[0] === key) {
            return tuple[1]
          }
        }
        //5.如果在bucket中没有找到,那么返回null
        return null
      }
      //remove方法:删除方法
      HashTable.prototype.remove = function (key)
      {
        //1.通过Key来调用哈希函数获取index
        let index = this.hashFunc(key, this.limit)
        //2.通过Index获取对应的bucket
        let bucket = this.storage[index]
        //3.判断bucket是否为空
        if (bucket === null || bucket === undefined) {
          return null
        }
        //4.如果存在就遍历查抄,删除
        for (let i = 0; i < bucket.length; i++) {
          let tuple = bucket[i]
          if (tuple[0] === key) {
            bucket.splice(i, 1)
            this.count--
            //判断是否需要缩容
            if (this.count < this.limit * 0.25) {
              const newSize = Math.floor(this.limit / 2)
              const primeSize = this.getPrime(newSize)
              this.resize(primeSize)
            }
            return tuple[1]
          }
        }
        //5.如果bucket中不存在返回null
        return null

      }

      //isEmpty 判断是否为空方法
      HashTable.prototype.isEmpyt = function ()
      {
        return this.count === 0
      }

      //size方法 获取size的方法
      HashTable.prototype.size = function ()
      {
        return this.count
      }

      //resize方法 扩容或者缩容的方法
      HashTable.prototype.resize = function (newLimit)
      {
        //存储原数组
        const oldStorage = this.storage
        //原属性重置
        this.storage = []
        this.limit = newLimit
        this.count = 0
        //遍历原数组,把每个索引的bucket中的每个值都取出来添加到扩容或者缩容的数组中
        for (let i = 0; i < oldStorage.length; i++) {
          //获取到bucket
          const bucket = oldStorage[i]
          //判断bucket是否为空
          if (bucket === null || bucket === undefined) {
            continue
          }
          //遍历每个bucket的每个值
          for (let j = 0; j < bucket.length; j++) {
            let tuple = bucket[j]
            this.put(tuple[0], tuple[1])
          }
        }
      }

      //isPrime方法  判断是否是质数
      HashTable.prototype.isPrime = function (num)
      {
        const temp = parseInt(Math.sqrt(num))
        for (let i = 2; i < temp; i++) {
          if (temp % 2 === 0) {
            return false
          }
        }
        return true
      }

      //getPrime 获取到质数size
      HashTable.prototype.getPrime = function (num)
      {
        while (!this.isPrime(num)) {
          num++
        }
        return num
      }

    }

    const hashTable = new HashTable()
    hashTable.put('abc', 18)
    hashTable.put('aaa', 20)
    hashTable.put('vvv', 1111)
    console.log(hashTable);
    console.log(hashTable.get('vvv'));
    hashTable.remove('aaa')
    console.log(hashTable);

image-20230522212904108

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值