【数据结构和算法】6. 哈希表

本文根据 数据结构和算法入门 视频记录

文章目录

  • 1. 哈希表的概念
    • 1.1 哈希表的实现方式
    • 1.2 哈希函数(Hash Function)
    • 1.3 哈希表支持的操作
  • 2. Java实现

在前几章的学习中,我们已经了解了数组和链表的基本特性,不管是数组还是链表,如果我们想要寻找一个特定的数值,时间复杂度都为O(n)。那有什么办法用最快的速度来找到一个特定的元素呢,今天我们就来学习工业界中常用的数据结构“哈希表”,在哈希表中,不管是寻找、删除、增加一个新元素,时间复杂度都是O(1)。

1. 哈希表的概念

哈希表以键值的方式来存储元素,也就是说每个数据都是以键值(key,value)的方式存储的,关键字(key)是不可重复的,而值是对应于键的。我们可以把哈希表简单理解为一本字典,每个键(key)是一个单词,而每个单词都有自己对应的解释,也就是值(value)。

1.1 哈希表的实现方式

那我们需要用什么数据结构实现哈希表呢?我们知道数组的特点是寻址容易,插入和删除数组困难。而链表的特点是寻址困难,插入和删除数据容易。那么我们能不能综合两者的特性,创建一种寻址容易,插入删除也容易的数据结构呢?是的,哈希表就使用这么一种聪明的实现方式:“拉链法”,可以简单理解为“一堆由链表组成的数组”,如图所示:
在这里插入图片描述
可以看到哈希表的左侧是数组,数组的每个成员包括一个指针,指向一个链表的头节点,当然链表可能为空,也可能链接多个节点。

我们存储键值对(key-value pair)的方式主要依靠键的特征,通过键的哈希值找到对应的数组下标,然后将其键值对添加到对应的链表中,寻找元素时,也是根据其键的哈希值,找到特定链表其对应的值。

那我们如何得到所谓的哈希值呢?我们先简单理解一下此过程:哈希表使用哈希函数(Hash Function)将键(key)转换成一个哈希值(整形数字),然后将该数组对数组长度取余,取余得到的数字就当做数组的下标,然后将其键值对添加到对应的链表中。寻找一个键所对应的值时,我们也是使用哈希函数将键转换为对应的数组下标,并在其链表中定位到该关键字所对应的数值。

1.2 哈希函数(Hash Function)

可见哈希表能快速添加和查找元素的核心原因就是哈希函数,哈希函数能快速将一个数值转换成哈希值(整数)。所以哈希表必须保持哈希值的计算一致,如果两个哈希值是不相同的,那么这两个哈希值的原始输入也是不相同的。

那如果两个不同的输入得到相同的哈希值呢?这也就是所谓的哈希值冲突,这也是为什么我们结合数组和链表来实现哈希表,如果一个关键字对应的数组下标已经有其他元素了,只需要在其对应的链表后创建一个新的节点即可。

简单来说,我们输入关键字x,使用哈希函数f(x)计算出哈希值y,然后使用哈希值y来找特定的数组下标,并在对应位置插入新数据。在之后的实现中,我们会使用Java来实现哈希表,而JVM自动生成哈希值,我们只需要再将其哈希值和我们的哈希表数组长度取模(mod)就能拿到其对应下标。

1.3 哈希表支持的操作

为了帮助大家更好地理解哈希表的原理,我们来详细描述一对键值被插入的过程,首先我们将键值对以链表节点的方式储存,其节点包含自己的键和值。如果我们要将一对键值存入哈希表,我们需要使用哈希函数计算键的哈希值,并与哈希表的长度取模,然后找到对应的数组下标,如果该位置没有其他键值节点,直接将其位置指向新节点即可。如果对应的位置有其他节点,直接将其新节点加到链表的最后面即可。

如果我们要查找一个键所对应的值,我们只需要计算该键对应的哈希值,找到数组下标所对应的头节点后,从头节点开始寻找我们所要的键值对。
在这里插入图片描述
以下哈希表支持的操作:

  • get(K key):通过特定的关键字拿到其所对应的值
  • add(Key key, Value value):将一对新的键值对加入哈希表
  • remove(Key key):通过关键字,删除哈希表中的键值对
  • getSize():当前键值对的数量
  • isEmpty():查看哈希表是否为空

2. Java实现

下面我们就来使用Java实现哈希表,在我们定义的键值对中,关键字为字符串类型,数值为整数类型,以下是哈希表和哈希表节点的定义:

import java.util.ArrayList;public class HashMap {static class HashNode<String, Integer> {String key;Integer value;HashNode<String, Integer> next;public HashNode(String key, Integer value) {this.key = key;this.value = value;}}private ArrayList<HashNode<String, Integer>> bucketArray;private int numBuckets;private int size;public HashMap() {bucketArray = new ArrayList<>();numBuckets = 10;size = 0;for(int i = 0; i < numBuckets; i++) {bucketArray.add(null);}}
}

HashNode就是键值对节点的定义,其中key就是关键字,而value是关键字对应的数值,还包含了next指向下一个节点。HashMap则是哈希表的定义,其中包含了数据类型为ArrayList的bucketArray,大家可以将其理解为哈希表图示左侧的Array,bucketArray中存储由HashNode组成的链表。HashMap还记录了numBuckets代表哈希表数组的长度(不是节点的数量),size代表当前bucket的数量。在哈希表初始化时,我们初始化bucketArray,然后给numBuckets设置一个初始值10,初始化size,并在每个bucketArray上加上一个空的头节点。

以下是getBucketIndex的定义:

private int getBucketIndex(String key) {int hashCode = key.hashCode();int index = hashCode % numBuckets;return index;
}

getBucketIndex的输入是一个关键字,输出是关键字对应的bucket位置。我们只需要通过Java内置的hashCode函数,将关键字转化成整数形式的hashCode,然后将hashCode和numBuckets取模,就能得到对应bucket的指数。以下是add的定义:

public void add(String key, Integer value) {int bucketIndex = getBucketIndex(key);HashNode<String, Integer> head = bucketArray.get(bucketIndex);while (head != null) {if (head.key.equals(key)) {head.value = value;return;}head = head.next;}size++;head = bucketArray.get(bucketIndex);HashNode<String, Integer> newNode = new HashNode<String, Integer>(key, value);newNode.next = head;bucketArray.set(bucketIndex, newNode);if ((1.0 * size) / numBuckets >= 0.7) {ArrayList<HashNode<String, Integer>> temp = bucketArray;bucketArray = new ArrayList<>();numBuckets = 2 * numBuckets;size = 0;for (int i = 0; i < numBuckets; i++) {bucketArray.add(null);}for (HashNode<String, Integer> headNode : temp) {while(headNode != null) {add(headNode.key, headNode.value);headNode = headNode.next;}}}
}

在add方法中,我们使用getBucketIndex拿到关键字对应的bucket指数,并从对应bucket的头节点开始,寻找是否有与其关键词对应的节点,如果找到其节点,更新节点的数据,然后返回。如果没有找到,我们创建一个新的键值对节点,然后将其next指针指向对应bukcet的头节点,再把新节点更新为对应bucket的头节点。如果当前键值对数量超过了bucket容量的70%,那么我们可以创建一个长度为当前数量两倍的bucketArray,并把当前的节点转移到新的bucketArray上。

get函数是通过关键字找到对应数组的函数:

public Integer get(String key) {int bucketIndex = getBucketIndex(key);HashNode<String, Integer> head = bucketArray.get(bucketIndex);while (head != null) {if (head.key.equals(key)) {return head.value;}head = head.next;}return null;
}

在此函数中,我们通过getBucketIndex拿到关键字对应的bucket指数,并拿到head头指针,然后顺着对应链表往下走,寻找是否有对应的关键字的节点,如果找到了,返回对应的数值,否则返回null。

public Integer remove(String key) {int bucketIndex = getBucketIndex(key);HashNode<String, Integer> head = bucketArray.get(bucketIndex);HashNode<String, Integer> prev = null;while (head != null) {if (head.key.equals(key))break;prev = head;head = head.next;}if (head == null) {return null;}size--;if (prev != null) {prev.next = head.next;} else {bucketArray.set(bucketIndex, head.next);}return head.value;
}

在remove函数中,我们通过getBucketIndex拿到对应的bucket指数,然后拿到头指针,通过迭代next指针,我们可以使用删除链表节点的方式来删除对应节点。最后是两个简单函数size和isEmpty的定义:

public int size() {return size;
}public boolean isEmpty() {return size() == 0;
}

以上就是哈希表的定义啦,可见哈希表的插入删除很快,可是当数组快满,要进行数据迁移的话,性能下降得非常严重,所以设计数组长度时,必须知道将要储存多少数据,才能设计出一个合理有效的哈希表。

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

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

相关文章

【python】如何将文件夹及其子文件夹下的所有word文件汇总导出到一个excel文件里?

根据你的需求,这里提供一套完整的Python解决方案,支持递归遍历子文件夹、提取Word文档内容(段落+表格),并整合到Excel中。以下是代码实现及详细说明: 一个单元格一个word的全部内容 完整代码 # -*- coding: utf-8 -*- import os from docx import Document import pand…

leetcode-位运算

位运算 371. 两整数之和 题目 给你两个整数 a 和 b &#xff0c;不使用 运算符 和 - &#xff0c;计算并返回两整数之和。 示例 1&#xff1a; 输入&#xff1a; a 1, b 2 输出&#xff1a; 3 示例 2&#xff1a; 输入&#xff1a; a 2, b 3 输出&#xff1a; 5 提示&am…

飞帆控件:在编辑模式下额外加载的库

飞帆是一个自由的控件设计平台。在飞帆中&#xff0c;我们可以很方便地创建基于 Vue 2 组件的控件&#xff0c;并使用控件来搭建网页。 他山之石&#xff0c;可以攻玉。在创建控件中&#xff0c;使用 js 、css 依赖库能让我们的控件更强大。 有些时候&#xff0c;在编辑模式下…

GPLT-2025年第十届团体程序设计天梯赛总决赛题解(共计266分)

今天偶然发现天梯赛的代码还保存着&#xff0c;于是决定写下这篇题解&#xff0c;也算是复盘一下了 L1本来是打算写的稳妥点&#xff0c;最后在L1-6又想省时间&#xff0c;又忘记了insert&#xff0c;replace这些方法怎么用&#xff0c;也不想花时间写一个文件测试&#xff0c…

编码转换器

大批量转换编码 可以将整个工程文件夹从GB18030转为UTF-8 使用Qt C制作 项目背景 比较老的工程&#xff0c;尤其是keil嵌入式的工程&#xff0c;其文本文件&#xff08;.c、.cpp、.h、.txt、……&#xff09;编码为gb2312&#xff0c;这为移植维护等带来了不便。现在uit-8用…

STL 核心模块

很好&#xff01;你想深入 STL&#xff08;Standard Template Library&#xff09;和容器算法&#xff0c;是学习 C 非常关键的一步。下面我给你整理一份STL 容器 算法的入门指南&#xff0c;适合从零起步掌握这部分内容。 &#x1f31f; 一、STL 核心模块 STL 分为三大块&am…

2024沈阳区域赛,D - Dot Product Game

题目链接 树状数组求逆序对 #include<bits/stdc.h> using namespace std; using lllong long; typedef pair<int,int>PII; typedef priority_queue<int> upq; typedef priority_queue<int,vector<int>,greater<int>> dpq; const int M99…

简易博客点赞系统实现

简易博客点赞系统 好久没写 Java 了&#xff0c;整个简单的项目进行康复训练。 基于 Spring Boot SSM MySQL Mybatis-Plus Knife4j Swagger 的一个简易博客点赞系统 开源地址&#xff1a;https://github.com/FangMoyu/simple-thumb 功能 登录获取当前登录用户获取博客…

一个既简单又诡异的问题

public class DaYaoGuai {static String s;public static void main(String[] args) {Thread t1 new Thread(){Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}s"深圳";}};t1.start();Thre…

使用docker在manjaro linux系统上运行windows和ubuntu

因为最近项目必须要使用指定版本的solidworks和maxwell&#xff08;都只能在win系统上使用&#xff09;, 且目前的ubuntu容器是没有桌面的&#xff0c;导致我运行不了一些带图形的ros2功能。无奈之下&#xff0c;决定使用docker-compose写一下配置文件&#xff0c;彻底解决问题…

Elasticsearch中的_source字段讲解

_source 在 Elasticsearch 查询中用于限制返回的字段,类似于 SQL 中的 SELECT 指定列。 代码示例: esSearchResults = es_service.search_documents({"query": {"terms": {"file_id":

【论文阅读20】-CNN-Attention-BiGRU-滑坡预测(2025-03)

这篇论文主要探讨了基于深度学习的滑坡位移预测模型&#xff0c;结合了MT-InSAR&#xff08;多时相合成孔径雷达干涉测量&#xff09;观测数据&#xff0c;提出了一种具有可解释性的滑坡位移预测方法。 [1] Zhou C, Ye M, Xia Z, et al. An interpretable attention-based deep…

C++ 的 IO 流

&#x1f4ac; &#xff1a;如果你在阅读过程中有任何疑问或想要进一步探讨的内容&#xff0c;欢迎在评论区畅所欲言&#xff01;我们一起学习、共同成长~&#xff01; &#x1f44d; &#xff1a;如果你觉得这篇文章还不错&#xff0c;不妨顺手点个赞、加入收藏&#xff0c;并…

spring cloud gateway前面是否必须要有个nginx

在 **"客户端 → Nginx (前置限流) → Spring Cloud Gateway → 微服务(Sentinel 熔断限流)"** 的架构中&#xff0c;**Spring Cloud Gateway 前面并不强制要求必须有 Nginx**&#xff0c;是否需要取决于具体场景。以下是详细分析&#xff1a; 一、必须使用 Nginx 的…

Spark和Hadoop之间的对比和联系

&#xff08;一&#xff09;Spark概述 Spark是一种基于内存的快速、通用、可拓展的大数据分析计算引擎。Hadoop是一个分布式系统基础架构。 1&#xff09;官网地址&#xff1a;Apache Spark™ - Unified Engine for large-scale data analytics 2&#xff09;文档查看地址&…

多线程进阶(Java)

注&#xff1a;此博文为本人学习过程中的笔记 1.常见的锁策略 当我们需要自己实现一把锁时&#xff0c;需要关注锁策略。Java提供的synchronized已经非常好用了&#xff0c;覆盖了绝大多数的使用场景。此处的锁策略并不是和Java强相关的&#xff0c;只要涉及到并发编程&#…

c++STL——stack、queue、priority_queue的模拟实现

文章目录 stack、queue、priority_queue的模拟实现使用部分模拟实现容器适配器deque的介绍原理真实结构deque的迭代器deque的操作deque的优缺点 stack的模拟实现按需实例化queue的模拟实现priority_queue的模拟实现为何引入仿函数代码实现 stack、queue、priority_queue的模拟实…

【深度学习—李宏毅教程笔记】Transformer

目录 一、序列到序列&#xff08;Seq2Seq&#xff09;模型 1、Seq2Seq基本原理 2、Seq2Seq模型的应用 3、Seq2Seq模型还能做什么&#xff1f; 二、Encoder 三、Decoder 1、Decoder 的输入与输出 2、Decoder 的结构 3、Non-autoregressive Decoder 四、Encoder 和 De…

C++镌刻数据密码的树之铭文:二叉搜索树

文章目录 1.二叉搜索树的概念2.二叉搜索树的实现2.1 二叉搜索树的结构2.2 二叉搜索树的节点寻找2.2.1 非递归2.2.2 递归 2.3 二叉搜索树的插入2.3.1 非递归2.3.2 递归 2.4 二叉搜索树的删除2.4.1 非递归2.4.2 递归 2.5 二叉搜索树的拷贝 3.二叉树的应用希望读者们多多三连支持小…

系统架构设计师:流水线技术相关知识点、记忆卡片、多同类型练习题、答案与解析

流水线记忆要点‌ ‌公式 总时间 (n k - 1)Δt 吞吐率 TP n / 总时间 → 1/Δt&#xff08;max&#xff09; 加速比 S nk / (n k - 1) | 效率 E n / (n k - 1) 关键概念 周期&#xff1a;最长段Δt 冲突‌&#xff1a; ‌数据冲突&#xff08;RAW&#xff09; → 旁路/…