Javascript数据结构——哈希表

18_哈希表_深入链地址法_哔哩哔哩_bilibili

哈希表(Hash Table),又称为散列表,是一种通过哈希函数组织数据以实现快速访问的数据结构。下面将从其概述、底层实现和前端应用场景等方面进行详细阐述。

概述

哈希表的基本思路是,设要存储的元素个数为n,设置一个长度为m(m≥n)的连续内存单元,以每个元素的关键字k为自变量,通过哈希函数把k映射为内存单元的地址(下标),并把该元素存储在这个内存单元中,如此构造的存储结构称为哈希表。简单来说,哈希表就像是一个巨大的书架,每个书架格都有一个编号(哈希值),而每个编号下都可以存放一本书(键值对)。当你想要找一本书时,只需知道它的编号,就能迅速定位到它所在的位置。

哈希函数是哈希表的核心,它接收一个输入(键),并返回一个输出(哈希值),这个哈希值通常是一个整数,用于确定键在表中的位置。理想情况下,哈希函数应该尽可能减少冲突(不同键产生相同哈希值的情况)。当两个键产生相同的哈希值时,就需要冲突解决机制。常见的冲突解决方法有开放寻址法和链地址法。

相关概念

一、哈希化

哈希化,又称散列,是通过关于键值(key)的函数,将数据映射到内存存储中的一个位置来访问的过程。这个过程称为哈希,而这个映射函数则称为散列函数或哈希函数。哈希化通过计算缩小范围,先分类再查找,从而加快查找速度

二、哈希函数

哈希函数是指将哈希表中元素的关键键值映射为元素存储位置的函数。表示为:Addr=H(key),其中Addr表示存储地址,H表示哈希函数,key表示关键字。

常见的哈希函数构造方法有:

  1. 直接定址法:取Key或者Key的某个线性函数值为散列地址。Hash(k)=k,或者Hash(k)=a×k+b,(a、b均为常数)。
  2. 数字分析法:需要知道Key的集合,并且Key的位数比地址位数多,选择Key数字分布均匀的位作为散列地址。
  3. 平方取中法:取Key平方值的中间几位作为Hash地址。因为在设置散列函数时不一定知道所有关键字,选取哪几位不确定。一个数的平方的中间几位和数本身的每一位都有关,这样可以使随机分布的Key得到的散列地址也是随机分布的。
  4. 折叠法:将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为哈希地址。当Key的位数较多且数字分布均匀时,适合采用这种方案。具体的叠加方法有两种:移位法和折叠法。
  5. 除留余数法:取关键字被某个除数p求余,得到的余数作为散列地址。即H(Key)=Key%p。这是最常用的构造哈希函数的方法。
  6. 随机数法:选择一个随机函数,取关键字的随机函数值为它的哈希地址,即H(key)=random(key),其中random为随机函数。通常用于关键字长度不等时采用此法。

哈希函数的选择应尽量减少产生冲突,即不同的Key值对应同一个Hash地址的情况。但不管选用何种散列函数,都不可避免地会产生冲突。

三、哈希表

哈希表(Hash Table)又称散列表,是一种数据结构,用于实现关联数组(Associative Array),即可以通过键(Key)来查找对应的值(Value)哈希表使用哈希函数将键转换为数组下标,从而实现快速查找、插入和删除操作。

哈希表的主要优点是查询速度非常快,时间复杂度接近O(1)。但是,哈希表也有缺点,即在数据量较大时,可能会出现哈希冲突。解决哈希冲突的方法有多种,如开放寻址法(Open Addressing)、链地址法(Chaining,又称拉链法)等。

  • 开放寻址法:如果h(k)已经被占用,则按一定的增量序列探查新的位置,直到找到一个空闲的位置为止。根据探查函数p(i)的不同,开放定址法又分为线性探查法、二次探查法、随机探查法和双散列函数法等。

不推荐:开放寻址,探测长度随着填充因子(数据项/哈希表长)增大而程指数趋势增长

  • 链地址法:将具有同一散列地址的记录存储在一条线性链表中。当发生冲突时,只需将新元素插入到对应链表的末尾即可。这种方法虽然解决了冲突问题,但增加了编程复杂度,并且可能导致链表过长,影响查找效率。

常见的哈希表实现有Java中的HashMap、Python中的字典(dict)等。

底层实现

  1. 哈希函数:哈希函数的设计至关重要,它决定了哈希表的性能和效率。一个好的哈希函数应该具有快速计算、均匀分布等特点。在JavaScript中,Object类型的哈希表实际上是通过链地址法来解决冲突的,即每个数组索引指向一个链表,链表中存储所有哈希值相同的键值对。

  2. 冲突解决

    • 链地址法:每个哈希表的槽位存储一个链表,所有哈希值相同的键值对都存储在这个链表中。这种方法在处理冲突时比较灵活,但可能会增加额外的空间开销。
    • 开放寻址法:当发生冲突时,通过一定的探测策略(如线性探测、二次探测、再哈希等)找到一个空闲的槽位来存储键值对。这种方法不需要额外的存储空间,但可能会增加查找时间。
  3. 动态扩容:当哈希表中的数据量增加到一定程度时,会进行动态扩容,以避免哈希冲突和保持查找效率。扩容通常涉及重新计算所有键值对的哈希值,并将它们重新插入到新的哈希表中。

前端应用场景

  1. 缓存系统:在Web开发中,经常需要将频繁访问的数据存储在缓存中以提高访问速度。哈希表以其快速查找的特性,成为了缓存系统的理想选择。

  1. 去重与计数:在处理需要快速去重或计数的场景时,哈希表表现出色。例如,在统计一个字符串中每个字符出现的次数时,可以使用哈希表来记录每个字符及其对应的计数。
  2. 路由表:在Web服务器中,路由表负责将URL映射到相应的处理函数。哈希表能够快速根据URL定位到处理函数,提高路由效率。
  3. 用户输入处理:在处理用户输入时,可以使用哈希表来快速检查输入数据是否已存在或是否满足某些条件。

总之,哈希表作为数据结构中的瑰宝,在JavaScript开发中发挥着不可替代的作用。通过深入理解哈希表的原理和实现方式,可以更好地利用这一强大工具来优化代码性能和提高开发效率。

手动实现哈希表

实现哈希表(Hash Table)通常涉及两个主要部分:哈希函数(hash function)和存储桶(buckets)。在 JavaScript 中,我们可以使用数组和链表来实现一个简单的哈希表。数组将作为存储桶的集合,而链表将用于处理哈希冲突(即多个键映射到同一个索引的情况)。

以下是一个简单的实现:

  1. 定义链表节点
    链表节点将存储键值对以及指向下一个节点的指针。
class ListNode {  
    constructor(key, value) {  
        this.key = key;  
        this.value = value;  
        this.next = null;  
    }  
}
  1. 定义哈希表
    哈希表将包含一个数组(存储桶)和一个哈希函数。
class HashTable {  
    constructor(size = 10) {  
        this.size = size;  
        this.buckets = new Array(size).fill(null);  
    }  
  
    // 简单的哈希函数  
    hashFunction(key) {  
        let hash = 0;  
        for (let i = 0; i < key.length; i++) {  
            hash = (hash * 31 + key.charCodeAt(i)) % this.size;  
        }  
        return hash;  
    }  
  
    // 插入键值对  
    set(key, value) {  
        const index = this.hashFunction(key);  
        const bucket = this.buckets[index];  
  
        if (bucket === null) {  
            // 如果桶为空,创建一个新的链表节点  
            this.buckets[index] = new ListNode(key, value);  
        } else {  
            // 如果桶不为空,遍历链表查找键或插入新节点  
            let currentNode = bucket;  
            while (currentNode !== null) {  
                if (currentNode.key === key) {  
                    // 如果键已存在,更新值  
                    currentNode.value = value;  
                    return;  
                }  
                if (currentNode.next === null) {  
                    // 链表末尾,插入新节点  
                    currentNode.next = new ListNode(key, value);  
                    return;  
                }  
                currentNode = currentNode.next;  
            }  
        }  
    }  
  
    // 获取值  
    get(key) {  
        const index = this.hashFunction(key);  
        const bucket = this.buckets[index];  
  
        if (bucket === null) {  
            // 如果桶为空,返回 undefined  
            return undefined;  
        } else {  
            // 遍历链表查找键  
            let currentNode = bucket;  
            while (currentNode !== null) {  
                if (currentNode.key === key) {  
                    return currentNode.value;  
                }  
                currentNode = currentNode.next;  
            }  
        }  
        // 如果键不存在,返回 undefined  
        return undefined;  
    }  
  
    // 删除键值对  
    remove(key) {  
        const index = this.hashFunction(key);  
        let bucket = this.buckets[index];  
  
        if (bucket === null) {  
            // 如果桶为空,不执行任何操作  
            return;  
        } else if (bucket.key === key) {  
            // 如果要删除的节点是桶的第一个节点  
            this.buckets[index] = bucket.next;  
        } else {  
            // 遍历链表查找并删除节点  
            let currentNode = bucket;  
            while (currentNode.next !== null) {  
                if (currentNode.next.key === key) {  
                    currentNode.next = currentNode.next.next;  
                    return;  
                }  
                currentNode = currentNode.next;  
            }  
        }  
    }  
}
  1. 使用哈希表
    现在我们可以创建哈希表实例并插入、获取和删除键值对。
const hashTable = new HashTable();  
hashTable.set('name', 'Alice');  
hashTable.set('age', 30);  
hashTable.set('city', 'New York');  
  
console.log(hashTable.get('name')); // 输出: Alice  
console.log(hashTable.get('age'));  // 输出: 30  
console.log(hashTable.get('city')); // 输出: New York  
  
hashTable.remove('age');  
console.log(hashTable.get('age'));  // 输出: undefined

这个简单的哈希表实现展示了如何使用数组和链表来处理哈希冲突,并通过哈希函数将键映射到数组索引。注意,这只是一个基础实现,实际生产中的哈希表可能会使用更复杂的哈希函数、动态数组大小调整(如哈希表的扩展和收缩)以及其他优化技术。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C#与C++交互开发系列(九):字符串传递的几种形式

前言 在C#与C交互开发中&#xff0c;字符串的传递是非常常见的需求。字符串作为数据类型在托管代码&#xff08;C#&#xff09;和非托管代码&#xff08;C&#xff09;之间的传递存在一些特殊的挑战&#xff0c;因为两者的字符串内存管理和编码方式不同。本篇博客将详细介绍几…

gitlab不同账号间·仓库转移

背景&#xff1a;公司业务调整&#xff0c;原先在海外仓库的代码转移回国内 诉求&#xff1a;完整的保留项目记录 操作&#xff1a; 步骤一: 定位到需要迁移的原项目地址 步骤二&#xff1a;创建新项目 步骤三&#xff1a;打开命令行&#xff0c;创建好文件路径为需要clo…

软件工程中的建造者模式:用于构建复杂对象

在软件工程中&#xff0c;我们经常会遇到需要构建复杂对象的场景。这些对象可能包含多个组件&#xff0c;而这些组件的创建过程可能相当繁琐。为了解决这个问题&#xff0c;设计模式提供了一种优雅的方法&#xff0c;这就是建造者模式&#xff08;Builder Pattern&#xff09;。…

HTTP之响应消息Response

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ HTTP之响应消息Response 1 Response 组成2 状态…

基于SpringBoot+Vue+MySQL的实践性教学系统

系统展示 用户前台界面 后台界面 系统背景 随着信息技术的快速发展&#xff0c;企业对于高效、智能的管理系统需求日益迫切。传统的管理系统大多采用单机版或C/S架构&#xff0c;存在操作复杂、维护困难、数据共享性差等问题。而基于SpringBootVueMySQL的全栈管理系统&#xff…

通信协议——UART

目录 基础概念串行&并行串行的优缺点 单工&双工 UART基本概念时序图思考&#xff1a;接收方如何确定01和0011 基础概念 串行&并行 串行为8车道&#xff0c;并行为1车道 串行的优缺点 通行速度快浪费资源布线复杂线与线之间存在干扰 单工&双工 单工&#xf…

018集——c# 实现CAD添加侧栏菜单(WPF控件)(CAD—C#二次开发入门)

本例实现的效果如下&#xff1a; 第一步&#xff1a;添加引用 using UserControl System.Windows.Controls.UserControl; using System.Windows.Forms.Integration;//PaletteSet integration 第二步 <UserControl x:Class"AcTools.UserControl1"xmlns"htt…

【数据分析】Power BI的使用教程

目录 1 Power BI架构1.1 Power BI Desktop1.2 Power BI服务1.3 Power BI移动版 2 Power Query2.1 Power Query编辑器2.2 Power Query的优点2.3 获取数据2.4 数据清洗的常用操作2.4.1 提升标题2.4.2 更改数据类型2.4.3 删除错误/空值2.4.4 删除重复项2.4.5 填充2.4.6 合并列2.4.…

【Airtest】 UI 自动化

一、环境配置 项目名称&#xff1a;Yavin 锁定python3.7.x和opencv-contrib-python3.4.2.17&#xff0c;不然各种报错 参考airtest官网https://airtest.doc.io.netease.com/ 虚拟环境配置 安装所需要的依赖包 二、框架使用方式 1.目录结构介绍 2.config文件config.yaml文…

前端开发设计模式——状态模式

目录 一、状态模式的定义和特点 二、状态模式的结构与原理 1.结构&#xff1a; 2.原理&#xff1a; 三、状态模式的实现方式 四、状态模式的使用场景 1.按钮的不同状态&#xff1a; 2.页面加载状态&#xff1a; 3.用户登录状态&#xff1a; 五、状态模式的优点 1.提…

【深度学习基础】详解Pytorch搭建CNN卷积神经网络实现手写数字识别

MNIST 数据集,其包含70000 个2828 的手写数字的数据集,其中又分为60000 个训练样本与10000 个测试样本。 安装实验用到的包 anaconda promt 安装python包, 首先在开始界面打开prompt 进入到相应的虚拟环境中,下面的python38你自己创建的虚拟环境名称。 # 激活虚拟环境,v…

Ubuntu 24.04 系统上配置 Node.js 运行环境

本文我们重点介绍两种安装 Node.js 的方法。第一种方法使用 NVM (Node VersionManager)&#xff0c;这是安装和管理多个 Node.js 版本的最好和最快的方法。第二种方法使用官方包存储库在 Ubuntu 上安装 Node.js&#xff0c;一次只允许安装一个版本。 必备条件 A running Ubun…

yarn的安装与使用以及与npm的区别(安装过程中可能会遇到的问题)

一、yarn的安装 使用npm就可以进行安装 但是需要注意的一点是yarn的使用和node版本是有关系的必须是16.0以上的版本。 输入以下代码就可以实现yarn的安装 npm install -g yarn 再通过版本号的检查来确定&#xff0c;yarn是否安装成功 yarn -v二、遇到的问题 1、问题描述…

特斯拉Optimus:展望智能生活新篇章

近日&#xff0c;特斯拉举办了 "WE ROBOT" 发布会&#xff0c;发布会上描绘的未来社会愿景&#xff0c;让无数人为之向往。在这场吸引全球无数媒体的直播中&#xff0c;特斯拉 Optimus 人形机器人一出场就吸引了所有观众的关注。从多家媒体现场拍摄的视频可以看出来&…

Ubuntu 上安装 Redmine 5.1 指南

文章目录 官网安装文档&#xff1a;命令步骤相关介绍GemRubyRailsBundler 安装 Redmine更新系统包列表和软件包&#xff1a;安装必要的依赖&#xff1a;安装 Ruby&#xff1a;安装 bundler下载 Redmine 源代码&#xff1a;安装 MySQL配置 Redmine 的数据库配置文件&#xff1a;…

Java 基于 poi 和 itextpdf 实现 excel 转 pdf

目录 问题 实现思路 pom Excel2PDFUtil Excel2PDFUtilTest 输出结果 问题 工作中遇到一个需求&#xff0c;需要实现 excel 文件的预览&#xff0c;实现的思路就是将 excel 转成 pdf 文件&#xff0c;上传到文件服务器上得到文件地址&#xff0c;预览时只需要返回 pdf 预…

Uni-App-02

条件编译 条件编译概念 不同的运行平台终归有些专有的特性&#xff0c;无法实现跨平台完全兼容&#xff0c;例如&#xff1a;微信小程序导航栏右上角的关闭图标。 uni-app提供了一种“条件编译”机制&#xff0c;可以针对特定的平台编译执行特定的代码&#xff0c;否则不执行。…

[JAVAEE] 线程安全的案例(一)-单例模式

目录 一. 单例模式 二. 单例模式的使用时机 三. 单例模式的关键代码 四. 单例模式的几种实现方式 4.1 饿汉方式(急) 4.2 懒汉模式(缓) a. 解决原子性的问题 b. 解决程序运行效率低下的问题 c. 解决指令重排序的问题(其次是为了解决内存可见性的问题) 五. 总结 一. …

【大模型实战篇】大模型分词算法BPE(Byte-Pair Encoding tokenization)及代码示例

词元化是针对自然语言处理任务的数据预处理中一个重要步骤&#xff0c;目的是将原始文本切分成模型可以识别和处理的词元序列。在大模型训练任务中&#xff0c;就是作为大模型的输入。传统的自然语言处理方法&#xff0c;如基于条件随机场的序列标注&#xff0c;主要采用基于词…

Nest.js 实战 (十五):前后端分离项目部署的最佳实践

☘️ 前言 本项目是一个采用现代前端框架 Vue3 与后端 Node.js 框架 Nest.js 实现的前后端分离架构的应用。Vue3 提供了高性能的前端组件化解决方案&#xff0c;而 Nest.js 则利用 TypeScript 带来的类型安全和模块化优势构建了一个健壮的服务端应用。通过这种技术栈组合&…