数据结构与算法——21.哈希表

这篇文章我们来学习哈希表。

1.什么是哈希表

下面来看一下哈希表

目录

1.什么是哈希表

1.1前言

1.2哈希表的介绍

2.哈希表的实现


1.1前言

在学习什么是哈希表之前,我们先来看下面在这一种情况。

        我这里有一堆数据,我给每个数据上一个编号,假设就是从0-10,然后,我准备一个表格,就是一个一维数组。现在,我数据有了,表格有了,下面,我在数据的编号和表格索引之间建立一个关系。假设把编号0的数据放到数组索引0的位置,把编号1的数据放到数组索引1的位置,以此类推的进行存储。那么现在,只要我知道我数据的编号,那么我就能立刻的查找到我的数据,并且时间复杂度为O(1),这个是很好理解的,就是数组的查找嘛。

        建立编号与表格索引的关系,将来就可以通过编号快速查找数据,在理想情况下,编号唯一,并且数组足够大能够容纳所有的数据。但是现实情况却是你不可能造一个容纳所有数据的数组,并且编号也可能会有重复。那怎么解决呢?我们可以通过拉链法来解决。当我们的数组放满后,再放入数据时,我们可以让新放入的数据的编号再与我们数组的一个索引构成映射关系,然后新方式的数据和数组中原数据以链表的形式穿成一串,以这种方式继续存储。这就是拉链法。对于数据中编号重复的问题,我们可以通过数据自身来进行区分。

        上面的例子中,我们是用数组+链表的方式解决了问题。

1.2哈希表的介绍

哈希表也叫散列表,它是一种数据结构,底层是由数组+链表实现的,它提供了快速的插入操作和查找操作,无论哈希表总中有多少条数据,插入和查找的时间复杂度都是为O(1),因为哈希表的查找速度非常快,所以在很多程序中都有使用哈希表,例如拼音检查器。

哈希表也有自己的缺点,哈希表是基于数组的,我们知道数组创建后扩容成本比较高,所以当哈希表被填满时,性能下降的比较严重

哈希表采用的是一种转换思想,其中一个重要的概念是如何将「键」或者「关键字」转换成数组下标就是数组索引?在哈希表中,这个过程由哈希函数来完成,哈希函数也是哈希表中非常重要的一部分,但是并不是每个「键」或者「关键字」都需要通过哈希函数来将其转换成数组下标,有些「键」或者「关键字」可以直接作为数组的下标。

2.哈希表的实现

下面,我们来看一下哈希表的实现

package Tree;
//哈希表的设计与实现
public class L6_HashTable {//节点类static class Entry{/*哈希码,可以理解为我们数据的一个编号,这个是我们根据key值,用hash算法算出来的,这个编号是要与数组的索引对应起来的*/int hash;Object key;//关键字,这个是我们人为设置的Object value;//值,这个就是存的具体数据了Entry next;//指向下一个节点的指针public Entry(int hash, Object key, Object value) {this.hash = hash;this.key = key;this.value = value;}}Entry[] table = new Entry[16];//最开始的那个数组,里面存链表头结点的地址int size = 0; //链表中元素的个数float loadFactor = 0.75f; //元素个数与链表长度的比值int threshold = (int) (loadFactor * table.length); //阈值/*** 求模运算替换为位运算*  前提:数组长度是2的n次方*  hash % 数组长度 等价于 hash &〔数组长度-1)* *///根据 hash 码 获取 ValueObject get(int hash, Object key){int idx = hash & (table.length - 1);//数据的索引位置if (table[idx] == null){return null;}Entry p = table[idx];while (p != null){if (p.key.equals(key)){return p.value;}p = p.next;}return null;}//向哈希表中存入新的key和value,如果key重复,则更新Valuevoid put(int hash, Object key, Object value){int idx = hash & (table.length - 1);//数据的索引位置if (table[idx] == null){table[idx] = new Entry(hash,key,value);//为空,新增操作size++;}else {Entry p = table[idx];while (true){if (p.key.equals(key)){p.value = value;//更新操作return;}if (p.next == null)break;p = p.next;}p.next = new Entry(hash,key,value);//新增size++;if(size > threshold){resize();//扩容}}}private void resize() {Entry[] newTable = new Entry[table.length << 1];for (int i = 0; i < table.length-1; i++) {Entry p = table[i];//拿到每个链表头if (p != null){//拆分链表,并移动到新数组/*** 拆分规律*      旧数组中,一个链表最多被拆分为两个链表*      hash & table.length == 0 的一组*      hash & table.length != 0 的一组* */Entry a = null;Entry b = null;Entry aHead = null;Entry bHead = null;while (p != null){if ((p.hash & table.length) == 0){if (a != null){a.next = p;}else {aHead = p;}a = p;//分配到a}else {if (b != null){b.next = p;}else {bHead = p;}b = p;//分配到b}p = p.next;}if (a != null){a.next = null;newTable[i] = aHead;}if (b != null){b.next = null;newTable[i+ table.length] = bHead;}}}table = newTable;//扩容完,用新数组代替旧数组threshold = (int) (loadFactor * table.length);}//根据 hash 码 删除,返回删除的ValueObject remove(int hash, Object key){int idx = hash & (table.length - 1);//数据的索引位置if (table[idx] == null){return null;}Entry p = table[idx];Entry prev = null;while (p != null){if (p.key.equals(key)){//找到了if (prev == null){table[idx] = p.next;}else {prev.next = p.next;//单向链表的删除操作}size--;return p.value;}prev = p;p = p.next;}return null;}
}

上面代码就是hashTable的实现了,总体来说不算太难,就是扩容时需要思考一下,其余的就是链表+数组的操作而已。

这个实现中没有写通过hash算法生成哈希码的方法,Hash算法的使用我会在后面单独出一篇文章来写,这里就先这样吧。

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

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

相关文章

如何应对MySQL单表数据量过大:垂直分表与水平分表策略解析

话接上回&#xff0c;单表最大数据建议两千万&#xff0c;那如果开发一个项目&#xff0c;预计注册量达到一个亿怎么办。 单表内放这么多数据&#xff0c;MYSQL底层B树的层级结构就可能会变得很高&#xff0c;磁盘io次数变多&#xff0c;性能会大幅度降低。所以考虑数据库分表…

解析CopyOnWrite机制 以java的CopyOnWriteArrayList为例

什么是CopyOnWrite 写时复制&#xff08;Copy-on-write&#xff0c;简称COW&#xff09;是读写分离的一种实现方式&#xff0c;因为读和写在不同的容器中。 核心思想&#xff1a;线程在修改数据的时&#xff0c;会将原数据复制一份&#xff0c;然后在副本上修改&#xff0c;最…

Macbook M3新机器安装cocoapods失败

这个问题昨天困扰了一个下午 中午拿到的全新的m3 2024 MacBook air&#xff0c;操作系统是Sonoma 14.3 安装Android studio与Flutter一切顺利 在安装cocoapods的时候&#xff0c; sudo gem install cocoapods 一直失败&#xff0c;开始是提示timeout&#xff0c;后来想办法…

一文读懂工作流原理及其在多元应用场景下的实践

一文读懂工作流原理及其在多元应用场景下的实践 引言工作流原理概述应用场景示例及详细描述1. 人力资源招聘流程2. 项目管理流程3. 财务报销审批流程4. 合同审核签署流程 总结 引言 工作流&#xff08;Workflow&#xff09;&#xff0c;简单来说&#xff0c;就是对业务流程进行…

imx.7交叉编译libX11

背景&#xff1a; 还是之前提到的触摸屏无响应问题&#xff0c;通过GDB调试&#xff0c;发现APP并非人为代码卡死&#xff0c;而是卡在官方的libc.so.6中&#xff0c;这个库出现了一些错误。排除自己代码问题&#xff0c;就剩官方版本问题&#xff0c;移植X11库&#xff0c;或…

提高APP安全性的必备加固手段——深度解析代码混淆技术

​ APP 加固方式 iOSAPP 加固是优化 APK 安全性的一种方法&#xff0c;常见的加固方式有混淆代码、加壳、数据加密、动态加载等。下面介绍一下 iOSAPP 加固的具体实现方式。 混淆代码&#xff1a; 使用 ProGuard 工具可以对代码进行混淆&#xff0c;使得反编译出来的代码很难…

前端本地搭建gninx环境

nginx下载地址&#xff1a; https://nginx.org/en/download.html nginx下载后&#xff0c;解压即用&#xff0c;注意解压目录不要含中文 nginx常用命令 查看版本 nginx -v 开启nginx服务 start nginx 重启服务 nginx -s reload 关闭服务 nginx -s stopnginx目录简析

three.js能够实现的3D动画效果大阅兵,有图有真相。

three.js能够实现许多不同类型的3D交互动画&#xff0c;包括但不限于以下几种&#xff1a; 旋转和缩放&#xff1a;可以通过鼠标或触摸手势来旋转和缩放3D模型或场景。 序列动画&#xff1a;可以创建复杂的动画序列&#xff0c;包括移动、旋转、缩放、颜色变化等。 粒子效果&…

使用Flask部署ppocr模型_3

PaddleOCR环境搭建、模型训练、推理、部署全流程&#xff08;Ubuntu系统&#xff09;_1_paddle 多进程推理-CSDN博客 PP-Structure 文档分析-CSDN博客 接前两篇继续完成Flask部署 一、使用Flask部署ppocr模型 GET方法用于从服务器获取资源&#xff0c;即客户端向服务器请求数据…

单链表讲解

一.链表的概念以及结构 链表是一种物理结构上不连续&#xff0c;逻辑结构上连续的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接次序实现的。 链表的结构与火车是类似的&#xff0c;一节一节的&#xff0c;数据就像乘客一样在车厢中一样。 与顺序表不同的…

线程池中核心线程数与最大线程数与缓冲任务队列的关系?

在Java中的线程池&#xff08;如ThreadPoolExecutor&#xff09;中&#xff0c;核心线程数&#xff08;corePoolSize&#xff09;、最大线程数&#xff08;maximumPoolSize&#xff09;以及缓冲队列&#xff08;workQueue&#xff09;之间存在着密切的关系&#xff0c;它们共同…

如何使用pytorch进行图像分类

如何使用pytorch进行图像分类https://featurize.cn/notebooks/5a36fa40-490e-4664-bf98-aa5ad7b2fc2f

【软考】UML中的图之用例图

目录 1. 说明2. 建模2.1 说明2.2 语境建模2.3 需求建模 3. 图示4. 组成部分 1. 说明 1.用例图&#xff08;Use Case Diagram&#xff09;。2.展现了一组用例、参与者&#xff08;Actor&#xff09;以及它们之间的关系。3.用例图通常包括以下的内容&#xff1a;用例、参与者、用…

配置IP地址并验证连通性

1.实验环境 主机 A和主机 B通过一根网线相连&#xff0c;如图6.13所示。 图6.13 实验案例一示意图 2.需求描述 为两台主机配置!P地址&#xff0c;验证P地址是否生效&#xff1b;验证同一网段的两台主机可以互通&#xff0c;不同网段的主机不能直接互通。 3.推荐步骤 为两台…

uni原生导航栏相关设置

动态设置某一项内容 使用场景&#xff1a;不同角色显示不同导航栏或设置不同名称&#xff0c;不同图标 API: uni.setTabBarItem(OBJECT) 属性类型默认值必填说明indexnumber无是tabBar的哪一项&#xff0c;从左边算起&#xff08;从0开始&#xff09;textString无否tab上的按…

【Linux】封装一下简单库 理解文件系统

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 一、封装一下简单库 二、理解一下stdin(0)、stdout(1)、stderr(3) 2.1、为什么要有0、1、2呢&#xff1f; 2.2、特点 2.3、如果我想让2也和1重定向到一个文件…

uni-app 微信小程序设置全局转发给朋友、分享到朋友圈

小程序右上角原生菜单自带的分享按钮&#xff0c;默认不可用 1.创建一个mixin share.js export default {created() {//#ifdef MP-WEIXINwx.showShareMenu({withShareTicket: true,menus: [shareAppMessage, shareTimeline]});//#endif}, }export default {created() {//#ifde…

Linux学习 - 常用和不太常用的实用awk命令

Linux学习 - 常用和不太常用的实用awk命令 Linux学习系列文章是生信宝典最开始主推的一块&#xff0c;力图从一个新额视角帮助初学者快速入门Linux系统&#xff0c;熟悉Linux下的文件和目录&#xff0c;文件操作&#xff0c; 文件内容操作。而且教程摒弃了完美操作&#xff0c…

immutable variables, constants的区别(rust)

let immutable_variable 1590865; //不可变变量 const constant_variable 0096; //常量常量、不可变变量一样&#xff0c;都无法更改变量的值&#xff0c;但是常量和不可变变量之间存在以下区别&#xff1a; 不可变变量默认不可变&#xff0c;编译器会进行检查。常量不仅在默…

vs2022断点调试怎么看堆栈帧,找异常位置

打一个断点以后&#xff0c;会出现如图报错 我们要怎么找到报错的语句&#xff1f;鼠标点击->堆栈帧->上一行运行的位置->直到找到错误出错如图所示&#xff1a; 跳转到&#xff0c;我们手写的代码&#xff0c;执行出错的位置