手撸数据结构之线性链表---哈希表(散列) Hash
哈希数据结构
哈希表的存在是为了解决能通过O(1)时间复杂度直接索引到指定元素。
这是什么意思呢?通过我们使用数组存放元素,都是按照顺序存放的,当需要获取某个元素的时候,则需要对数组进行遍历,获取到指定的值
而这样通过循环遍历比对获取指定元素的操作,时间复杂度是O(n),也就是说如果你的业务逻辑实现中存在这样的代码是非常拉胯的。那怎么办呢?这就引入了哈希散列表的设计。
哈希散列虽然解决了获取元素的时间复杂度问题,但大多数时候这只是理想情况。因为随着元素的增多,很可能发生哈希冲突,或者哈希值波动不大导致索引计算相同,也就是一个索引位置出现多个元素情况。
当李二狗、拎瓢冲都有槽位的下标索引03的 叮裆猫 发生冲突时,情况就变得糟糕了,因为这样就不能满足O(1)时间复杂度获取元素的诉求了。
那么此时就出现了一系列解决方案,包括;HashMap 中的拉链寻址 + 红黑树、扰动函数、负载因子、ThreadLocal 的开放寻址、合并散列、杜鹃散列、跳房子哈希、罗宾汉哈希等各类数据结构设计。让元素在发生哈希冲突时,也可以存放到新的槽位,并尽可能保证索引的时间复杂度小于O(n)
实现哈希散列
public class HashMap01<K, V> implements Map<K, V> {private final Object[] tab = new Object[8];@Overridepublic void put(K key, V value) {int idx = key.hashCode() & (tab.length - 1);tab[idx] = value;}@Overridepublic V get(K key) {int idx = key.hashCode() & (tab.length - 1);return (V) tab[idx];}}
@Test
public void test_hashMap01() {Map<String, String> map = new HashMap01<>();map.put("01", "花花");map.put("02", "豆豆");logger.info("碰撞前 key:{} value:{}", "01", map.get("01"));// 下标碰撞map.put("09", "蛋蛋");map.put("12", "苗苗");logger.info("碰撞前 key:{} value:{}", "01", map.get("01"));
}
拉链寻址
既然我们没法控制元素不碰撞,但我们可以对碰撞后的元素进行管理。比如像 HashMap 中拉链法一样,把碰撞的元素存放到链表上。
public class HashMap02BySeparateChaining<K, V> implements Map<K, V> {private final LinkedList<Node<K, V>>[] tab = new LinkedList[8];@Overridepublic void put(K key, V value) {int idx = key.hashCode() & (tab.length - 1);if (tab[idx] == null) {tab[idx] = new LinkedList<>();tab[idx].add(new Node<>(key, value));} else {tab[idx].add(new Node<>(key, value));}}@Overridepublic V get(K key) {int idx = key.hashCode() & (tab.length - 1);for (Node<K, V> kvNode : tab[idx]) {if (key.equals(kvNode.getKey())) {return kvNode.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;}}}
因为元素在存放到哈希桶上时,可能发生下标索引膨胀,所以这里我们把每一个元素都设定成一个 Node 节点,这些节点通过 LinkedList 链表关联,当然你也可以通过 Node 节点构建出链表 next 元素即可。
那么这时候在发生元素碰撞,相同位置的元素就都被存放到链表上了,获取的时候需要对存放多个元素的链表进行遍历获取。
测试
@Test
public void test_hashMap02() {Map<String, String> map = new HashMap02BySeparateChaining<>();map.put("01", "花花");map.put("05", "豆豆");logger.info("碰撞前 key:{} value:{}", "01", map.get("01"));// 下标碰撞map.put("09", "蛋蛋");map.put("12", "苗苗");logger.info("碰撞前 key:{} value:{}", "01", map.get("01"));
}
开放寻址
除了对哈希桶上碰撞的索引元素进行拉链存放,还有不引入新的额外的数据结构,只是在哈希桶上存放碰撞元素的方式。它叫开放寻址,也就是 ThreaLocal 中运用斐波那契散列+开放寻址的处理方式。
package com.lm.hash;public class HashMap03ByOpenAddressing<K, V> implements Map<K, V> {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);} else {for (int i = idx; i < tab.length; i++) {if (tab[i] == null) {tab[i] = new Node<>(key, value);break;}}}}@Overridepublic V get(K key) {int idx = key.hashCode() & (tab.length - 1);for (int i = idx; i < tab.length; i ++){if (tab[idx] != null && tab[idx].key == key) {return tab[idx].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;}}
}
开放寻址的设计会对碰撞的元素,寻找哈希桶上新的位置,这个位置从当前碰撞位置开始向后寻找,直到找到空的位置存放。
在 ThreadLocal 的实现中会使用斐波那契散列、索引计算累加、启发式清理、探测式清理等操作,以保证尽可能少的碰撞。
public void test_hashMap03() {Map<String, String> map = new HashMap03ByOpenAddressing<>();map.put("01", "花花");map.put("05", "豆豆");logger.info("碰撞前 key:{} value:{}", "01", map.get("01"));// 下标碰撞map.put("09", "蛋蛋");map.put("12", "苗苗");logger.info("碰撞前 key:{} value:{}", "01", map.get("01"));
}
合并散列
合并散列是开放寻址和单独链接的混合,碰撞的节点在哈希表中链接。此算法适合固定分配内存的哈希桶,通过存放元素时识别哈希桶上的最大空槽位来解决合并哈希中的冲突。
合并散列的最大目的在于将碰撞元素链接起来,避免因为需要寻找碰撞元素所发生的循环遍历。也就是A、B元素存放时发生碰撞,那么在找到A元素的时候可以很快的索引到B元素所在的位置。
相对于直接使用开放寻址,这样的挂在链路指向的方式,可以提升索引的性能。因为在实际的数据存储上,元素的下一个位置不一定空元素,可能已经被其他元素占据,这样就增加了索引的次数。所以使用直接指向地址的方式,会更好的提高索引性能
杜鹃散列
杜鹃鸟在孵化
手撸数据结构之线性链表---哈希表(散列) Hash相关推荐
- 【数据结构笔记39】哈希表/散列表、(数据关键字/字符串关键字)散列构造函数
本次笔记内容: 11.1.1 引子:散列的基本思路 11.1.2 什么是散列表 11.2.1 数据关键词的散列函数构造 11.2.2 字符串关键词的散列函数构造 文章目录 散列表背景 基本思想引出 已 ...
- C++--哈希表--散列--冲突--哈希闭散列模拟实现--问答--1107
1.哈希 概念 可以不经过任何比较,直接从表中得到要搜索的元素. 关键在于通过某种散列函数,使元素的存储位置与它的关键码之间能够建立 一一映射的关系.这样就可以通过o(1)的时间复杂度来寻找到元素. ...
- 数据结构实验之查找七:线性之哈希表
数据结构实验之查找七:线性之哈希表 Time Limit: 1000MS Memory Limit: 65536KB Submit Statistic Problem Description 根据给定 ...
- SDUT 3379 数据结构实验之查找七:线性之哈希表
数据结构实验之查找七:线性之哈希表 Time Limit: 1000 ms Memory Limit: 65536 KiB Problem Description 根据给定的一系列整数关键字和素数p, ...
- 【除留余数法定义hash函数+线性探测法解决hash冲突】数据结构实验之查找七:线性之哈希表
Think: 1知识点:除留余数法定义hash函数+线性探测法解决hash冲突 数据结构实验之查找七:线性之哈希表 Time Limit: 1000MS Memory Limit: 65536KB P ...
- 数据结构:哈希表(散列表)基础
哈希表(散列表)基础 引入哈希表 什么是哈西表: 一种具有相同特性的数据元素的集合,每个元素具有唯一标识自己的关键字. 基本原理: 说明: 顺序查找.二分查找或者二叉树的查找是基于待查关键字与表中元素 ...
- 哈希表(散列表)介绍
目录 前言 一.哈希概念 1.1 什么时哈希表 1.2 哈希函数 1.3 哈希冲突 1.4 哈希冲突的解决 1.4.1 闭散列 1.4.2 开散列 1.4.3 问题 前言 哈希表时C++11两容器un ...
- 2.10_hash_table_哈希表 / 散列表
链表类 class LinkedList(object):"""链表类"""class Node(object):def __init__( ...
- 哈希(散列):C语言实现 静态哈希表
哈希(散列)的概念,我们可以在上一篇文章中看到: https://blog.csdn.net/mowen_mowen/article/details/82943192 在这里,我们将使用C代码来实现一 ...
最新文章
- Python全栈开发,Day1 - Python基础1
- ABP(现代ASP.NET样板开发框架)系列之2、ABP入门教程
- mvn编写主代码与测试代码
- 推荐几个Android开发非常有用的工具(for android studio)
- mysql server启动_mysql的启动方式
- 使用 ale.js 制作一个小而美的表格编辑器(4)
- 显著性目标检测matlab代码_YOLO v3 目标检测终篇(附完整 GitHub 代码)
- 小程序手写板电子签名
- 电压比较器和运算放大器的区别
- MATLAB命令总结
- ps计算机二级自学教程,计算机二级考试《Photoshop图像处理与制作》
- 我国CN域名一年减少600万个 全要求实名注册
- 盘点2022年爆火的小程序游戏
- 嵌入式学习中较好的练手项目和课题整理(附代码资料、学习视频和嵌入式学习规划)
- 什么是和包(NFC)业务
- 怎么做视频特效?不妨试试抖音特效创作平台
- 多亏了这几款软件,我才能坚持写博客这么多年!
- 车联网赋能末端物流自动驾驶探索
- ker矩阵是什么意思_如何理解CAN通信矩阵
- 青龙BOT机器人交互
热门文章
- 夏柔免费API管理系统
- Cesium加载3D Tiles数据
- 自动填写问卷星并提交
- 查看域名https证书的起止时间
- If you have database settings to be loaded from a particular profile you may need to activate it
- If you have database settings to be loaded from a particular profile you may之oss文件上传遇到的问题
- 深入浅出TensorFlow2函数——tf.constant
- 【音视频第1天】常见的术语含义等
- Oculus Touch再度被坑,这次是电商Saturn流出上架信息
- phtoshop python api for mac