数据结构哈希表(散列) 之Hash

声明: 此文章仅限于记录学习之用 , 受限于自身水平和理解能力 , 因此结论可能是不正确的. 如果您需要学习,建议参考其他文章

看了下网上一些大佬的教程, 写的云山雾绕的. 简单总结下吧. 以言简意赅为主.

介绍下hash

hash 就是把任意输入通过算法生成一个int值. 这个值就是放数据的地址, 然后在这个地址中存储数据.
注意: 不同的内容可能生成相同的哈希码, 这就是我们常说的hash冲突. 如何处理hash冲突问题,衍生了以下几套经典算法.
在这里插入图片描述

使用演示hashMap 存取的过程.

根据key获取到 hashCode, 取到内存地址, 然后把value存入此区域.
在这里插入图片描述
获取值也是同样道理.
在这里插入图片描述
上图是最简易的hash存取示范. 结合刚刚说的"不同内容的hashCode可能相同", 因此是有hash冲突覆盖的情况.

解决hash冲突的常见方式

拉链寻址算法

从名字入手,可以更好的理解. 众所周知除了阿里巴巴喜欢胡乱造词, 大部分命名都有比较贴切的含义. 我看了下示例代码, 原来"拉链"不是衣服上的拉锁. 而是增加了y轴维度. 如果地址相同, 那就纵向排列. 拼多多这图最适合.

一图胜千言

示例代码
package hash_table;import java.util.LinkedList;/*** 拉链寻址的优点是可以有效地处理大量的哈希冲突,因为每个槽都可以包含一个链表,可以容纳更多的元素。* 然而,它也有一些缺点。* 例如,如果哈希表中有许多空槽,则可能会浪费大量内存,因为它需要为每个槽分配空间以存储链表头指针.* 此外,如果链表变得很长,则搜索元素所需的时间可能会增加。* @param <K>* @param <V>*/
public class HashMapBySeparateChaining<K, V> {//定义一个存储链表的数组private final LinkedList<Node<K, V>>[] arr = new LinkedList[8];/*** 插入元素:首先计算元素的哈希值,并将其存储在哈希表中的相应槽中。然后,将元素添加到该槽中的链表中。* @param key* @param value*/public void put(K key, V value) {int index = key.hashCode() & (arr.length - 1);//如果此地址是空的, 直接创建一个链表, 将内容存进去if (arr[index] == null) {arr[index] = new LinkedList<>();arr[index].add(new Node<>(key, value));} else {//如果此地址已经被占用了(hashCode冲突).就在链表中新增arr[index].add(new Node<>(key, value));}}/*** 查找元素:首先计算元素的哈希值,并找到其在哈希表中的相应槽。然后,在该槽的链表中搜索该元素。* @param key* @return*/public V get(K key) {int idx = key.hashCode() & (arr.length - 1);for (Node<K, V> kvNode : arr[idx]) {if (key.equals(kvNode.getKey())) {return kvNode.value;}}return null;}/*** 定义实体类* @param <K>* @param <V>*/static class Node<K, V> {final K key;V value;public Node(K key, V value) {this.key = key;this.value = value;}public K getKey() {return key;}public V getValue() {return value;}}}
特点
  • 拉链寻址的优点是可以有效地处理大量的哈希冲突,因为每个槽都可以包含一个链表,可以容纳更多的元素。
  • 然而,它也有一些缺点。
  • 例如,如果哈希表中有许多空槽,则可能会浪费大量内存,因为它需要为每个槽分配空间以存储链表头指针.
  • 此外,如果链表变得很长,则搜索元素所需的时间可能会增加。

开放寻址算法

开放寻址算法,开放就是没有明确划分位置的,比如公共教室的座位, 地铁的座位,火车站大厅的座椅等…就是我们理解的随便坐. 比如你去上课, 你肯定有个最喜欢的位置,一般情况就坐那. 但是你的位置被占了, 作为新时代文明青年, 你不好去赶走人家, 只能从这个位置往后找,直到找到第一个空座就直接坐下了.
你可能问为啥是找到第一个空座就坐下, 这个这个生活场景中不好解释. 但是在哈希表中是为了节约空间,减少空槽
在这里插入图片描述
请注意,需要把教室想象成一个哈希表(一维数组) .

示例代码
package hash_table;import com.alibaba.fastjson.JSON;/*** 开放寻址是一种解决哈希表中冲突的方法。* 当插入一个新的关键字时,如果发现该关键字对应的哈希地址已被其他关键字占用,* 则从当前哈希地址开始,按某种探查顺序连续探测可用的空地址,直至找到一个空地址为止。* @author Administrator* @param <K>* @param <V>*/
public class HashMapByOpenAddressing<K, V> {private final Node<K, V>[] arr = new Node[8];public void put(K key, V value) {int index = key.hashCode() & (arr.length - 1);//如果此哈希地址为空,就直接存放if (arr[index] == null) {arr[index] = new Node<>(key, value);} else {//如果哈希地址被占用了, 就往后找空槽存进去for (int i = index; i < arr.length; i++) {if (arr[i] == null) {arr[i] = new Node<>(key, value);break;}}}}public V get(K key) {int idx = key.hashCode() & (arr.length - 1);//从hash地址开始往后找, 直到找到后返回for (int i = idx; i < arr.length; i++) {if (arr[i] != null && arr[i].key == key) {return arr[i].value;}}return null;}static class Node<K, V> {final K key;V value;public Node(K key, V value) {this.key = key;this.value = value;}public K getKey() {return key;}public V getValue() {return value;}}@Overridepublic String toString() {return "HashMap{" +"arr=" + JSON.toJSONString(arr) +'}';}}
特点

开放寻址的缺点很明显, 在get的时候, 如果产生hashCode冲突需要向后遍历获取, 效率太低了. 下面的合并散列来解决此问题.

合并散列

合并散列就是在开放寻址的基础上,进行了优化, 解决了查询时遍历数据效率过低的问题. 具体做法是,如果出现hashCode冲突, 向后找空槽存入, 原对象指向新对象. 表达不清晰,大家看下下图试试理解.

在这里插入图片描述
在这里插入图片描述

示例代码
package hash_table;import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.util.Objects;/*** 合并散列(Coalesced hashing)是单独链接和开放寻址的混合,其中桶或节点在表中链接。该算法非常适合固定内存分配。通过识别哈希表上索引最大的空槽来解决合并哈希中的冲突,然后将冲突值插入该槽中。桶还链接到插入节点的插槽,其中包含其冲突哈希地址。*/
public class HashMap04ByCoalescedHashing<K, V> implements Map<K, V> {private final Logger logger = LoggerFactory.getLogger(HashMap04ByCoalescedHashing.class);private final Node<K, V>[] tab = new Node[8];@Overridepublic void put(K key, V value) {int idx = key.hashCode() & (tab.length - 1);//未冲突直接保存if (tab[idx] == null) {tab[idx] = new Node<>(key, value);return;}//key相同 value覆盖if (Objects.equals(tab[idx].key, key)) {tab[idx] = new Node<>(key, value);return;}//hash冲突时//找个下标int cursor = tab.length - 1;while (tab[cursor] != null && tab[cursor].key != key) {--cursor;}//把hash冲突的元素存起来tab[cursor] = new Node<>(key, value);// 将碰撞节点指向这个新节点while (tab[idx].idxOfNext != 0) {idx = tab[idx].idxOfNext;}tab[idx].idxOfNext = cursor;}@Overridepublic V get(K key) {int idx = key.hashCode() & (tab.length - 1);while (tab[idx].key != key) {idx = tab[idx].idxOfNext;}return tab[idx].value;}static class Node<K, V> {final K key;V value;int idxOfNext;public Node(K key, V value) {this.key = key;this.value = value;}public K getKey() {return key;}public V getValue() {return value;}public int getIdxOfNext() {return idxOfNext;}public void setIdxOfNext(int idxOfNext) {this.idxOfNext = idxOfNext;}}@Overridepublic String toString() {return "HashMap{" +"tab=" + JSON.toJSONString(tab) +'}';}}
特点

请注意,合并散列寻址并不是常见的哈希表冲突解决策略。常用的冲突解决策略包括线性探测、二次探测和链地址法等。合并散列寻址更常用于特定场景下的优化。

布谷鸟散列算法

待更新

跳房子散列算法

待更新

罗宾汉哈希算法

待更新

参考资料

图片来源

  1. 拉链寻址原图来自拼多多商品.
  2. 开放寻址原图来自中国海洋大学官网
  3. 合并散列原图来自大河网新闻, 小傅哥 bugstack 虫洞栈

内容来源:
部分解释参考自: https://gitcode.com/search Ai搜索
目录结构及部分算法参考自小傅哥 bugstack 虫洞栈 (技术很好,但个人感觉他教程写的着实一般)

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

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

相关文章

如何使用 Supabase Auth 在您的应用程序中设置身份验证

在本文中&#xff0c;您将学习基本的关键概念&#xff0c;这些概念将帮助您掌握身份验证和授权的工作原理。 您将首先了解什么是身份验证和授权&#xff0c;然后了解如何使用 Supabase auth 在应用程序中实现身份验证。 &#xff08;本文内容参考&#xff1a;java567.com&…

Spark入门01-Spark简介

1 Spark是什么 Spark是用于大规模数据处理的统一分析引擎。对任意类型的数据进行自定义计算。 可以计算&#xff1a;结构化、非结构化&#xff0c;半结构化的数据结构&#xff0c;支持使用Python&#xff0c;Java&#xff0c;Scala、Sql语言开发应用程序计算数据。 计算框架&a…

微服务—Docker

目录 初识Docker Docker与虚拟机的区别 镜像与容器 Docker架构 常见Docker命令 镜像命令 容器命令 数据卷挂载 直接挂载 初识Docker 在项目部署的过程中&#xff0c;如果出现大型项目组件较多&#xff0c;运行环境也较为复杂的情况&#xff0c;部署时会碰到一些问题&…

【力扣经典面试题】189. 轮转数组

题目描述&#xff1a; 给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数。 示例 1: 输入: nums [1,2,3,4,5,6,7], k 3 输出: [5,6,7,1,2,3,4] 解释: 向右轮转 1 步: [7,1,2,3,4,5,6] 向右轮转 2 步: [6,7,1,2,3,4,5] 向右轮转 …

探索Viper-适用于GoLang的完整配置解决方案

前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站https://www.captainbed.cn/kitie。 对于现代应用程序&#xff0c;尤其大中型的项目来说&#xff0c;在程序启动和运行时&#xff0c;往…

【Docker与微服务】基础篇

1 Docker简介 1.1 docker是什么 1.1.1 问题&#xff1a;为什么会有docker出现&#xff1f; 假定您在开发一个项目&#xff0c;您使用的是一台笔记本电脑而且您的开发环境具有特定的配置。其他开发人员身处的环境配置也各有不同。您正在开发的应用依赖于您当前的配置且还要依…

【Linux】yum与vim命令详解

&#x1f497;个人主页&#x1f497; ⭐个人专栏——Linux学习⭐ &#x1f4ab;点击关注&#x1f929;一起学习C语言&#x1f4af;&#x1f4ab; 目录 导读1. yum命令1.1 基本使用1.2 注意事项1.3 lrzsz软件包示例 2. vim命令2.1 vim的基本概念2.2 vim配置2.3 vim的基本操作2.3…

JSON巨匠:FastJSON的序列化解析

Fastjson 简介 Fastjson 是一个 Java 库&#xff0c;可以将 Java 对象转换为 JSON 格式&#xff0c;当然它也可以将 JSON 字符串转换为 Java 对象。 Fastjson 可以操作任何 Java 对象&#xff0c;即使是一些预先存在的没有源码的对象。 Fastjson 源码地址&#xff1a;https://…

【零基础学习CAPL】——CAN报文的发送(按下按钮同时周期性发送)

🙋‍♂️【零基础学习CAPL】系列💁‍♂️点击跳转 文章目录 1.概述2.面板创建3.系统变量创建4.CAPL实现4.1.函数展示4.2.全量报文展示5.效果1.概述 本章主要介绍使用CAPL和Panel在按下按钮时发送周期性CAN报文。 本章主要在“【零基础学习CAPL】——CAN报文的发送(配合P…

面对近期行情大起大落的伦敦银需要关注什么?

近期经常有听到投资者抱怨说&#xff0c;伦敦银价格没有明显趋势&#xff0c;很难做。确实&#xff0c;我们从日线图看&#xff0c;金价处于一个比较宽幅的横盘区间当中&#xff0c;近期的行情也是大涨大跌。投资者认为&#xff0c;面对大起大落的行情无从下手。下面我们就来讨…

第十篇【传奇开心果短博文系列】鸿蒙开发技术点案例示例:深度解读鸿蒙全场景适配

传奇开心果短博文系列 系列短博文目录鸿蒙开发技术点案例示例系列 短博文目录前言一、鸿蒙全场景适配实现介绍二、统一核心示例代码三、设备驱动框架示例代码四、统一界面框架示例代码五、自适应布局示例代码六、分布式能力示例代码七、跨平台开发示例代码八、设备能力开放示例…

数学建模-多目标规划

例&#xff1a;求下列函数最大值 Matlab 程序&#xff1a; 若分开求解&#xff0c;即分别求出第一个函数和第二个函数的最大值&#xff0c;我们试一下。 第一个函数最大值&#xff08;我们先求最小值&#xff09; c[3 -2];A[2,3;2,1];b[18;10];Aeq[];beq[];vlb[0;0];vub[];[…