详细版:哈希表(Hash Table)哈希冲突及其解决方法

哈希冲突的来源

哈希冲突是指不同的键(Key)经过哈希函数计算后,映射到相同的索引(Index)位置。由于哈希表的大小是有限的,而键的集合通常是无限的,因此冲突是不可避免的。例如,如果哈希表的大小为10,而两个不同的键 key1 和 key2 经过哈希函数计算后都得到相同的哈希值 hash(key1) = hash(key2) = 3,那么它们都会被存储在索引为3的位置。

解决哈希冲突的方法

下面详细介绍两种主要的哈希冲突解决方法:链地址法(Chaining)和开放地址法(Open Addressing),并通过图表进行更直观的说明。

链地址法(Chaining)

在链地址法中,哈希表的每个索引位置(也称为“桶”或“槽”)存储一个链表(或其他数据结构,如数组、树),所有映射到该位置的键值对都被存储在这个链表中。这种方法简单直观,适合冲突较多的情况。

链地址法的示意图

+-----------------+
|      索引0      |
|    (key1, v1)   |
+-----------------+
|      索引1      |
|    (key2, v2)   |
+-----------------+
|      索引2      |
|    (key3, v3) ->|
|    (key4, v4)   |
+-----------------+
|      索引3      |
|    (key5, v5)   |
+-----------------+

链地址法的优点

  1. 实现简单:直接使用链表即可。
  2. 内存利用率高:只有在冲突时才需要额外的存储空间。
  3. 动态大小:链表的长度可以动态调整,不会因为冲突而影响性能。

链地址法的缺点

  1. 链表遍历开销:在最坏情况下(所有元素都映射到同一个索引),查找操作的时间复杂度会退化为 O(n)。
  2. 空间复杂度:每个链表节点需要额外的指针空间。

链地址法的实现示例(Java)

class HashNode<K, V> {K key;  // 键V value;  // 值HashNode<K, V> next;  // 指向下一个节点的指针// 构造函数public HashNode(K key, V value) {this.key = key;this.value = value;this.next = null;  // 初始时,下一个节点为空}
}class HashTable<K, V> {private int size;  // 当前元素数量private int capacity;  // 哈希表容量private HashNode<K, V>[] buckets;  // 存储桶的数组// 构造函数,初始化哈希表public HashTable(int capacity) {this.size = 0;this.capacity = capacity;this.buckets = new HashNode[capacity];  // 初始化桶数组}// 获取键的哈希值并计算对应的桶索引private int getBucketIndex(K key) {int hashCode = key.hashCode();  // 获取键的哈希值return Math.abs(hashCode) % capacity;  // 取绝对值并取模,得到桶索引}// 插入键值对public void insert(K key, V value) {int index = getBucketIndex(key);  // 获取键对应的桶索引HashNode<K, V> head = buckets[index];  // 获取该桶的头节点while (head != null) {if (head.key.equals(key)) {head.value = value;  // 如果键已存在,更新值return;}head = head.next;  // 遍历链表}size++;  // 增加元素数量head = buckets[index];  // 重新获取头节点HashNode<K, V> newNode = new HashNode<>(key, value);  // 创建新节点newNode.next = head;  // 将新节点插入链表头部buckets[index] = newNode;  // 更新桶的头节点}// 获取键对应的值public V get(K key) {int index = getBucketIndex(key);  // 获取键对应的桶索引HashNode<K, V> head = buckets[index];  // 获取该桶的头节点while (head != null) {if (head.key.equals(key)) {return head.value;  // 找到键,返回对应的值}head = head.next;  // 遍历链表}return null;  // 未找到键,返回null}// 删除键值对public void remove(K key) {int index = getBucketIndex(key);  // 获取键对应的桶索引HashNode<K, V> head = buckets[index];  // 获取该桶的头节点HashNode<K, V> prev = null;  // 初始化前一个节点while (head != null) {if (head.key.equals(key)) {break;  // 找到键,跳出循环}prev = head;  // 更新前一个节点head = head.next;  // 遍历链表}if (head == null) return;  // 未找到键,直接返回size--;  // 减少元素数量if (prev != null) {prev.next = head.next;  // 前一个节点的指针指向后一个节点} else {buckets[index] = head.next;  // 如果删除的是头节点,直接更新桶的头节点}}
}
开放地址法(Open Addressing)

在开放地址法中,所有元素都存储在哈希表的数组中,当发生冲突时,通过某种探测策略(如线性探测、二次探测、双重散列等)寻找下一个可用位置。

开放地址法的示意图

+-----------------+
|      索引0      |
|    (key1, v1)   |
+-----------------+
|      索引1      |
|    (key2, v2)   |
+-----------------+
|      索引2      |
|    (key3, v3)   |
+-----------------+
|      索引3      |
|    (key4, v4)   |
+-----------------+
|      索引4      |
|    (key5, v5)   |
+-----------------+

开放地址法的优点

  1. 内存连续:所有元素存储在连续的内存空间中,访问速度快。
  2. 无额外空间:不需要额外的链表或其他数据结构。

开放地址法的缺点

  1. 探测开销:当冲突较多时,需要多次探测才能找到空闲位置,性能下降。
  2. 删除操作复杂:需要标记删除位置或者移动元素,增加了操作的复杂性。

开放地址法的实现示例(Java)

class HashTable<K, V> {private int size;  // 当前元素数量private int capacity;  // 哈希表容量private K[] keys;  // 存储键的数组private V[] values;  // 存储值的数组// 构造函数,初始化哈希表public HashTable(int capacity) {this.size = 0;this.capacity = capacity;this.keys = (K[]) new Object[capacity];  // 初始化键数组this.values = (V[]) new Object[capacity];  // 初始化值数组}// 获取键的哈希值并计算对应的桶索引private int getBucketIndex(K key) {int hashCode = key.hashCode();  // 获取键的哈希值return Math.abs(hashCode) % capacity;  // 取绝对值并取模,得到桶索引}// 线性探测函数private int linearProbe(int index, int i) {return (index + i) % capacity;  // 线性探测,返回下一个索引}// 插入键值对public void insert(K key, V value) {int index = getBucketIndex(key);  // 获取键对应的桶索引for (int i = 0; i < capacity; i++) {int probeIndex = linearProbe(index, i);  // 获取探测索引if (keys[probeIndex] == null || keys[probeIndex].equals(key)) {if (keys[probeIndex] == null) {size++;  // 如果该位置为空,增加元素数量}keys[probeIndex] = key;  // 插入键values[probeIndex] = value;  // 插入值return;}}throw new RuntimeException("HashTable is full");  // 哈希表已满,抛出异常}// 获取键对应的值public V get(K key) {int index = getBucketIndex(key);  // 获取键对应的桶索引for (int i = 0; i < capacity; i++) {int probeIndex = linearProbe(index, i);  // 获取探测索引if (keys[probeIndex] == null) {return null;  // 如果该位置为空,未找到键,返回null}if (keys[probeIndex].equals(key)) {return values[probeIndex];  // 找到键,返回对应的值}}return null;  // 未找到键,返回null}// 删除键值对public void remove(K key) {int index = getBucketIndex(key);  // 获取键对应的桶索引for (int i = 0; i < capacity; i++) {int probeIndex = linearProbe(index, i);  // 获取探测索引if (keys[probeIndex] == null) {return;  // 如果该位置为空,未找到键,直接返回}if (keys[probeIndex].equals(key)) {keys[probeIndex] = null;  // 删除键values[probeIndex] = null;  // 删除值size--;  // 减少元素数量return;}}}
}
其他解决哈希冲突的方法

除了链地址法和开放地址法,还有其他一些解决哈希冲突的方法,如:

  1. 再哈希(Rehashing):当哈希表负载因子超过一定阈值时,重新调整哈希表的大小并重新计算所有元素的位置。
  2. 布谷鸟哈希(Cuckoo Hashing):使用多个哈希函数,将冲突的元素放置到另一个哈希表中。
总结

哈希冲突是哈希表使用中不可避免的问题,但通过合理的解决方法,可以有效地管理和缓解冲突的影响。链地址法和开放地址法是两种主要的冲突解决策略,各有优缺点,大家需要根据具体应用场景和需求选择合适的方法。

希望你喜欢这篇文章!请点关注和收藏吧。祝关注和收藏的帅哥美女们今年都能暴富。如果有更多问题,欢迎随时提问

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

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

相关文章

海外云手机怎样助力亚马逊店铺运营?

随着全球化的发展&#xff0c;越来越多的企业开始重视海外市场的拓展&#xff0c;尤其是出海企业和B2B外贸企业。亚马逊作为全球最大的电商平台之一&#xff0c;成为了许多企业争夺国际市场的重点战场。本文将深入分析海外云手机在优化亚马逊店铺引流中的作用和优势&#xff0c…

怿星科技薛春宇丨智能汽车软件研发工具链国产化的挑战和探索

2024年7月25日&#xff0c;由上海良益企业管理咨询有限公司主办的“2024域控制器技术论坛“在上海成功举办&#xff0c;十位嘉宾做了精彩分享。“整零有道”将陆续刊出部分演讲的文字实录&#xff0c;以飨读者。 本期刊出怿星科技副总经理薛春宇的演讲实录&#xff1a;智能汽车…

如何用 obdiag 排查 OceanBase数据库的卡合并问题——《OceanBase诊断系列》14

1. 背景 卡合并在OceanBase中是一个复杂的问题&#xff0c;其产生可能源于多种因素。目前&#xff0c;对于卡合并的明确界定尚不存在统一标准&#xff0c;一方面&#xff0c;我们界定超过36小时未完成合并为合并超时&#xff0c;此时RS会记录ERROR日志&#xff1b;另一方面&am…

ArkUI自定义TabBar组件

在ArkUI中的Tabs&#xff0c;通过页签进行内容视图切换的容器组件&#xff0c;每个页签对应一个内容视图。其中内容是图TabContent作为Tabs的自组件&#xff0c;通过给TabContent设置tabBar属性来自定义导航栏样式。现在我们就根据UI设计的效果图来实现下图效果&#xff1a; 根…

Python | Leetcode Python题解之第508题出现次数最多的子树元素和

题目&#xff1a; 题解&#xff1a; class Solution:def findFrequentTreeSum(self, root: TreeNode) -> List[int]:cnt Counter()def dfs(node: TreeNode) -> int:if node is None:return 0sum node.val dfs(node.left) dfs(node.right)cnt[sum] 1return sumdfs(r…

设计模式(二)工厂模式详解

设计模式&#xff08;二&#xff09;工厂模式详解 简单工厂模式指由一个工厂对象来创建实例,适用于工厂类负责创建对象较少的情况。例子&#xff1a;Spring 中的 BeanFactory 使用简单工厂模式&#xff0c;产生 Bean 对象。 工厂模式简介 定义&#xff1a;工厂模式是一种创建…

AnaTraf | 全面掌握网络健康状态:全流量的分布式网络性能监测系统

AnaTraf 网络性能监控系统NPM | 全流量回溯分析 | 网络故障排除工具AnaTraf网络流量分析仪是一款基于全流量&#xff0c;能够实时监控网络流量和历史流量回溯分析的网络性能监控与诊断系统&#xff08;NPMD&#xff09;。通过对网络各个关键节点的监测&#xff0c;收集网络性能…

【计算机网络 - 基础问题】每日 3 题(五十七)

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?typeblog &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/fYaBd &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞…

代谢组数据分析(二十):通过WGCNA识别核心代谢物

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍识别核心基因加载R包导入数据数据预处理检查数据完整性计算软阈值soft根据软阈值构建接矩阵和拓扑重叠矩阵聚类并构建网络拓扑重叠热图查看具体模块的代谢物表达热图识别表型相关模…

sharpkeys-键盘部分按键不好用,用其它不常用按键代替

sharpkeys-键盘部分按键不好用&#xff0c;用其它不常用按键代替 文章目录

linux网络编程5——Posix API和网络协议栈,使用TCP实现P2P通信

文章目录 Posix API和网络协议栈&#xff0c;使用TCP实现P2P通信1. socket()2. bind()3. listen()4. connect()5. accept()6. read()/write(), recv()/send()7. 内核tcp数据传输7.1 TCP流量控制7.2 TCP拥塞控制——慢启动/拥塞避免/快速恢复/快速重传 8. shutdown()9. close()9…

Python游戏开发超详细第二课/一个小游戏等制作过程(入门级篇共2节)

直播内容&#xff0c;这里都用大多用照片代替了哈&#xff0c;因为在写一遍很累&#xff0c;哥哥姐姐理解一下抱歉抱歉 一个是我懒的写一遍&#xff0c;但是刚学的兄弟姐妹可不许学我偷懒哈 二防止有人偷懒&#xff0c;直接复制粘贴代码&#xff0c;所以为了方便帮助你们学习&a…

基于docker 部署redis

1、拉取镜像 docker pull redis:latest如果拉取失败可以尝试下配置镜像源&#xff0c;具体参考如下&#xff0c;目前暂可以使用 Docker切换镜像源-CSDN博客 2、创建配置文件 mkdir /usr/local/redis/conf vim redis.conf bind 0.0.0.0#protected-mode no port 6379 tcp-b…

新手直播方案

简介 新手直播方案 &#xff0c;低成本方案 手机/电脑 直接直播手机软件电脑直播手机采集卡麦电脑直播多摄像机 机位多路采集卡 多路麦加电脑&#xff08;高成本方案&#xff09; 直播推流方案 需要摄像头 方案一 &#xff1a;手机 电脑同步下载 网络摄像头 软件&#xff08…

【南方科技大学】CS315 Computer Security 【Lab6 IoT Security and Wireless Exploitation】

目录 Introduction (Part 1: OS Security for IoT )Software RequirementsStarting the Lab 6 Virtual MachineSetting up the Zephyr Development EnvironmentDownload the Zephyr Source CodeInstalling Requirements and DependenciesSetting the Project’s Environment Va…

【linux】服务器Ubuntu20.04安装cuda11.8教程

【linux】服务器Ubuntu20.04安装cuda11.8教程 文章目录 【linux】服务器Ubuntu20.04安装cuda11.8教程到官网找到对应版本下载链接终端操作cudnn安装到官网下载下载后解压进入解压后的目录&#xff1a;将头文件复制到 /usr/local/cuda/include/ 目录&#xff1a;将库文件复制到 …

利用客户端导入有关联的业务数据(DBeaver+sql)

前言 最近有点坑&#xff0c;麻辣烫的活落手上了&#xff0c;上个迭代除了自己的开发任务&#xff0c;还有处理接手的工作。然后节后问题又多&#xff0c;还有前1个迭代没有测试的模块本迭代测试&#xff0c;烦死了。 这次这个数据处理的活&#xff0c;以后希望可以交出…

mac电脑设置chrome浏览器语言切换为日语英语等不生效问题

在chrome中设置了语言&#xff0c;并且已经置顶了&#xff0c;但是不生效&#xff0c;在windows上直接有设置当前语言为chrome显示语言&#xff0c;但是mac上没有。 解决办法 在系统里面有一个单独给chrome设置语言的&#xff1a; 单独给它设定成指定的语言&#xff0c;然后重…

川渝地区计算机考研择校分析

C哥专业提供——计软考研院校选择分析专业课备考指南规划 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 根据最新数据分析,5所高校计算机专业2025年考研难度从高到低预计为: 电子科技大学 >> 四川大学 > 重庆大学 ≈ 西南交通大学 > 西南…

Vision-Language Models for Vision Tasks: A Survey阅读笔记

虽然LLM的文章还没都看完&#xff0c;但是终究是开始看起来了VLM&#xff0c;首当其冲&#xff0c;当然是做一片文献综述啦。这篇文章比较早了&#xff0c;2024年2月份出的last version。 文章链接&#xff1a;https://arxiv.org/abs/2304.00685 GitHub链接&#xff1a;GitHu…