实现哈希表

news2024/11/15 22:30:51

一:什么是哈希表?

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

在 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/805205.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IOS + Appium自动化教程

前言 项目闲置下来了&#xff0c;终于抽空有时间搞自动化了&#xff0c;看了下网上的教程基本通篇都是android自动化的介绍 &#xff0c;ios自动化方面的内容网上简介的少之可怜。由于本人对ios自动化也是第一次做&#xff0c;甚至对苹果电脑的使用都不太熟悉&#xff0c;花了大…

微信小程序,商城底部工具栏的实现

效果演示&#xff1a; 前提条件&#xff1a; 去阿里云矢量图标&#xff0c;下载8个图标&#xff0c;四个黑&#xff0c;四个红&#xff0c;如图&#xff1a; 新建文件夹icons&#xff0c;把图标放到该文件夹&#xff0c;然后把该文件夹移动到该项目的文件夹里面。如图所示 app…

vue3如何封装接口

&#x1f642;博主&#xff1a;锅盖哒 &#x1f642;文章核心&#xff1a;如何封装接口 目录 前言 1.首先&#xff0c;安装并导入axios库。你可以使用npm或yarn来安装&#xff1a; 2.创建一个api.js文件来管理接口封装&#xff1a; 3.在Vue组件中使用封装的接口&#xff1…

【雕爷学编程】MicroPython动手做(02)——尝试搭建K210开发板的IDE环境4

7、使用串口工具 &#xff08;1&#xff09;连接硬件 连接 Type C 线&#xff0c; 一端电脑一端开发板 查看设备是否已经正确识别&#xff1a; 在 Windows 下可以打开设备管理器来查看 如果没有发现设备&#xff0c; 需要确认有没有装驱动以及接触是否良好 &#xff08;2&a…

Ubuntu更改虚拟机网段(改成桥接模式无法连接网络)

因为工作需要&#xff0c;一开始在安装vmware和虚拟机时&#xff0c;是用的Nat网络。 现在需要修改虚拟机网段&#xff0c;把ip设置成和Windows端同一网段&#xff0c;我们就要去使用桥接模式。 环境&#xff1a; Windows10、Ubuntu20.04虚拟机编辑里打开虚拟网络编辑器&#…

安装Harbor

前言 Harbor是一个用于存储和分发Docker镜像的企业级Registry服务器&#xff0c;虽然Docker官方也提供了公共的镜像仓库&#xff0c;但是从安全和效率等方面考虑&#xff0c;部署企业内部的私有环境Registry是非常必要的&#xff0c;Harbor和docker中央仓库的关系&#xff0c;…

第四章:Spring上

第四章&#xff1a;Spring上 4.1&#xff1a;Spring简介 Spring概述 官网地址&#xff1a;https://spring.io/。 Spring是最受欢迎的企业级的java应用程序开发框架&#xff0c;数以百万的来自世界各地的开发人员使用Spring框架来创建性能好、易于测试、可重用的代码。Spring框…

【多模态】18、ViLD | 通过对视觉和语言知识蒸馏来实现开集目标检测(ICLR2022)

文章目录 一、背景二、方法2.1 对新类别的定位 Localization2.2 使用 cropped regions 进行开放词汇检测2.3 ViLD 三、效果 论文&#xff1a;Open-vocabulary Object Detection via Vision and Language Knowledge Distillation 代码&#xff1a;https://github.com/tensorflo…

Verilog语法学习——LV10_使用函数实现数据大小端转换

LV10_使用函数实现数据大小端转换 题目来源于牛客网 [牛客网在线编程_Verilog篇_Verilog快速入门 (nowcoder.com)](https://www.nowcoder.com/exam/oj?page1&tabVerilog篇&topicId301) 题目 描述 在数字芯片设计中&#xff0c;经常把实现特定功能的模块编写成函数&…

HBuilder 编辑器终端窗口无法输入,未响应的解决方案

HBuilder 编辑器终端窗口无法输入&#xff0c;未响应的解决方案 一、找到 HBuilder 安装目录 找到 main.js HBuilderX - plugins - builtincef3terminal - script - main.js 二、编辑 main.js 将 main.js 文件中的 powershell.exe 和 cmd.exe 路径都改为绝对路径 C:/Windows…

【渗透测试】漏洞扫描AWVS安装使用教程,三分钟手把手教会,非常简单

一、AWS简介 Acunetix Web Vulnerability Scanner(简称AWVS)是一个自动化的Web漏洞扫描工具&#xff0c;它可以扫描任何通过Web浏览器访问和遵循HITP/HTTPS规则的Web站点。 AWVS原理是基于漏洞匹配方法&#xff0c;通过网络爬虫测试你的网站安全&#xff0c;检测流行安全AWVS可…

Hadoop学习日记-YARN组件

YARN(Yet Another Resource Negotiator)作为一种新的Hadoop资源管理器&#xff0c;是另一种资源协调者。 YARN是一个通用的资源管理系统和调度平台&#xff0c;可为上层应用提供统一的资源管理和调度 YARN架构图 YARN3大组件&#xff1a; &#xff08;物理层面&#xff09…

Spring学习笔记,包含Spring IOC、AOP基本原理、Bean管理、Spring 事务等等

&#x1f600;&#x1f600;&#x1f600;创作不易&#xff0c;各位看官点赞收藏. 文章目录 Spring 基础笔记1、控制反转 (IOC)1.1、IOC 底层原理1.2、IOC 之Bean管理 ( XML )1.3、IOC 之Bean管理 (FactoryBean)1.4、Bean的作用域1.5、Bean的生命周期1.6、Bean的自动装配1.7、I…

SFP6002-ASEMI代理海矽美快恢复二极管参数、尺寸、规格

编辑&#xff1a;ll SFP6002-ASEMI代理海矽美快恢复二极管参数、尺寸、规格 型号&#xff1a;SFP6002 品牌&#xff1a;ASEMI 封装&#xff1a;TO-247AB 恢复时间&#xff1a;30ns 正向电流&#xff1a;60A 反向耐压&#xff1a;200V 芯片大小&#xff1a;102MIL*2 芯…

几个影响 cpu cache 性能因素及 cache 测试工具介绍

》内核新视界文章汇总《 文章目录 1 cache 性能及影响因素1.1 内存访问和性能比较1.2 cache line 对性能的影响1.3 L1 和 L2 缓存大小1.4 指令集并行性对 cache 性能的影响1.5 缓存关联性对 cache 的影响1.6 错误的 cacheline 共享 (缓存一致性)1.7 硬件设计 2 cpu cache benc…

【EI/SCOPUS会议征稿】第四届机器学习与计算机应用国际学术会议(ICMLCA 2023)

ICMLCA 2023 第四届机器学习与计算机应用国际学术会议 2023 4th International Conference on Machine Learning and Computer Application 第四届机器学习与计算机应用国际学术会议(ICMLCA 2023)定于2023年10月27-29日在中国杭州隆重举行。本届会议将主要关注机器学习和计算…

rk3568 Debian11 如何打开热点

思路&#xff1a;1. 下载必要工具&#xff08;hostapt、dnsmasq&#xff09;2. 配置网络&#xff08;无线网卡配置静态IP&#xff09;3. 配置hostapt配置文件4. 配置DHCP服务5. 启动服务&#xff08;hostapd/dnsmasq/network&#xff09;6. IP转发&#xff08;这一步决定了是否…

【QT 网络云盘客户端】——登录界面功能的实现

目录 1.注册账号 2.服务器ip地址和端口号设置 3. 登录功能 4.读取配置文件 5.显示主界面 1.注册账号 1.点击注册页面&#xff0c;将数据 输入 到 用户名&#xff0c;昵称&#xff0c;密码&#xff0c;确认密码&#xff0c;手机&#xff0c;邮箱 的输入框中&#xff0c; 点…

Vue3 导出word

&#x1f642;博主&#xff1a;锅盖哒 &#x1f642;文章核心&#xff1a;导出word 目录 1.首先&#xff0c;你需要安装docxtemplater库。可以使用npm或yarn来安装&#xff1a; 2.在Vue组件中&#xff0c;你可以使用docxtemplater来生成Word文档并提供一个导出按钮供用户下载…

线性表之顺序表

在计算机科学中&#xff0c;数据结构是非常重要的基础知识之一。数据结构为我们提供了组织和管理数据的方法和技巧&#xff0c;使得我们可以高效地存储、检索和操作数据。而顺序表作为数据结构中最基本、最常用的一种存储结构&#xff0c;也是我们学习数据结构的第一步。 本文将…