数据结构进阶 哈希桶

news2024/11/15 13:24:58

作者:@小萌新
专栏:@数据结构进阶
作者简介:大二学生 希望能和大家一起进步!
本篇博客简介:模拟实现高阶数据结构 哈希桶

哈希桶

  • 哈希冲突的另一种解决方法
    • 开散列 -- 链地址法
    • 举例
  • 哈希表的开散列实现 --哈希桶
    • 哈希表的结构
    • 哈希表的插入
    • 哈希表的查找
    • 哈希表的删除

哈希冲突的另一种解决方法

开散列 – 链地址法

首先对关键码集合使用哈希函数来计算哈希地址 具有相同地址的关键码归于同一子集
每一个子集称为一个桶
每个桶通过一个单链表连接起来 各链表的头节点存放在哈希表中

举例

例如我们使用除留余数法将序列{1, 6, 15, 60, 88, 7, 40, 5, 10}插入到表长为10的哈希表中

当发生哈希冲突的时候我们采取开散列的形式 将哈希地址相同的元素连接到同一个哈希桶下

抽象图大概是这样子
在这里插入图片描述

闭散列解决哈希冲突是一种比较野蛮的方式 即我的位置被抢了就去抢别人的位置

而开散列的解决方式就是我们的位置被抢了也没关系 我可以挂在下面

和闭散列不同的是 开散列将相同哈希地址通过单链表连接起来 不会影响与自己不同的哈希地址的增删查改效率 因此开散列的负载因子相比于闭散列而言可以稍微大一点

  • 闭散列的负载因子一般在0~0.7之间
  • 开散列的负载因子一般在0~1.0之间

因此在实际中 开散列的哈希桶比闭散列的哈希表更加实用

  1. 哈希桶的负载因子可以更大 空间利用率更高
  2. 哈希桶在极端情况下还有其他的解决方案

下面我们来看一下哈希桶的极端情况

假设我们要插入的元素集合是这样子的{3, 13, 23, 33, 43, 53, 63, 73, 83}

那么我们看看会变成什么样子

在这里插入图片描述
是不是直接退化成单链表了 那么这个时候查找元素的时间复杂度就退化成O(N)了

但是呢 这个时候我们可以将这个单链表转化成红黑树

这样子是不是时间复杂度又被我们优化到Log(N)了 这就是一种底线思维 即考虑最差的情况

为了避免上面说的这种极端情况的出现 一般桶中的节点个数超过8时 我们会将其转化为红黑树

当桶中的节点个数被删除至小于等于八时 我们会将其转化为单链表

哈希表的开散列实现 --哈希桶

哈希表的结构

在开散列的哈希表中 哈希表的每个位置存储的实际上是某个单链表的头结点 即每个哈希桶中存储的数据实际上是一个节点类型 该节点类型除了储存所给数据之外 还要指向下一个节点

节点类的代码如下


template<class K, class V>
struct HashNode
{
	pair<K, V> _kv;
	HashNode<K, V>* _next;

	HashNode(const pair<K, V>& kv) // 列表初始化构造函数
		:_kv(kv),
		_next(nullptr)
	{}
};

和闭散列不同的是 我们不需要为我们哈希表的每一个位置设置一个状态

因为在开散列当中我们将所有经过哈希函数计算后相同的地址放在同一个桶中

我们只需要去桶中寻找 如果桶中没有就是没有了

哈希表的开散列实现形式也需要根据负载因子来判断是否需要扩容 所以我们应该增加一个数据来存储哈希表中的有效元素个数 当负载因子过大时候进行扩容

哈希表的结构大体如下


template<class K, class V>
class HashTable
{
	typedef HashNode<K, V> Node;
public:
private:
	vector<Node*> _table;
	int _n;

};

哈希表的插入

哈希表插入的步骤如下

  1. 查看哈希表中是否存在该键值对 若存在则插入失败
  2. 判断是否需要调整哈希表的大小
  3. 将键值对插入哈希表
  4. 调整哈希表的大小

其中哈希表的调整方式如下

  • 如果哈希表的大小为0 则扩容至10
  • 如果哈希表的负载因子等于1 则先创建一个新哈希表 新的哈希表大小为原来的两倍 之后遍历原哈希表 将其中的所有元素放到新哈希表中 最后将原哈希表和新哈希表交换即可

与上一篇文章中的插入不同的是 我们这里不需要复用插入函数
只需要遍历原节点的时候改变每个节点的位置就可以了

将键值对插入哈希表的方式如下

  1. 通过哈希函数计算出对应的哈希地址
  2. 若产生哈希冲突 则直接将该结点头插到对应单链表即可
	bool Insert(const pair<K, V>& kv)
	{
		// 1. 查看哈希表中是否存在该键值对
		Node* ret = find(kv.first);
		if (ret)
		{
			return false; // 这里就说明找到了这个节点 如是直接返回false 表示插入失败
		}

		// 2. 判断是否需要调整哈希表的大小
		if (_n == _table.size()) // 哈希表的大小为0 此时负载因子我们定义为1
		{
			vector<Node*> newtable;
			size_t newsize = _table.size() == 0 ? 10 : _table.size() * 2;
			newtable.resize(newsize);


			// 将原哈希表中的所有节点插入到新哈希表中
			for (size_t i = 0; i < _table.size(); i++)
			{
				if (_table[i])// 单链表存在
				{
					Node* cur = _table[i];
					while (cur)
					{
						Node* next = cur->_next;
						size_t index = cur->_kv.first % newtable.size();
						// 将该节点的头插到新哈希表中编号为index的哈希桶中
						cur->_next = newtable[index];//第一次是空 后面就是上面的插入的节点
						newtable[index] = cur;

						cur = next;
					}
					_table[i] = nullptr;
				}
			}
			_table.swap(newtable);
		}

		// 开始插入
		size_t index = kv.first % _table[index];
		Node* newnode = new Node(kv);
		newnode->_next = _table[index];
		_table[index] = newnode;

		_n++;
		return true;
	}

哈希表的查找

哈希表的查找分为以下几步

  1. 先判断哈希表中的大小是否为0 为0则查找失败
  2. 通过哈希函数计算出一个哈希地址
  3. 在这个地址的单链表上寻找 找到则返回 找不到就返回空指针

代码表示如下

Node* Find(const K& key)
	{
		// 首先判断大小是否为0
		if (_table.size()==0)
		{
			return nullptr;
		}
		// 其次经过哈希函数计算找到函数地址
		size_t index = key % _table.size();
		Node* cur = _table[index];

		while (cur)
		{
			if (cur->_kv.first == key)
			{
				return cur;
			}
			cur = cur->_next;
		}
		return nullptr;
	}

哈希表的删除

哈希表的删除实际上就是单链表的删除

大体步骤如下

  1. 通过哈希函数计算出对应的哈希桶编号
  2. 遍历对应的哈希桶 寻找待删除结点
  3. 若找到了待删除结点 则将该结点从单链表中移除并释放
  4. 删除结点后 将哈希表中的有效元素个数减一

代码表示如下

	bool Erase(const K& key)
	{
		// 首先找编号
		size_t index = key % _table.size();
		// 之后开始找被删除阶段
		Node* prev = nullptr;
		Node* cur = _table[index];
		while (cur) // cur为空说明遍历完了还没找到直接返回false
		{
			if (cur->_kv.first == key)
			{
				if (prev == nullptr)
				{
					_table[index] = nullptr;
				}
				else
				{
					prev->_next = cur->_next;
				}
				delete cur;
				_n--;
				return true;
			}

			prev = cur;
			cur = cur->_next;
		}
		return false;
	}

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

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

相关文章

自动化测试Selenium【基础篇二】

自动化测试Selenium【基础篇二】&#x1f34e;一.Selenium基础使用&#x1f352;1.1 信息打印&#x1f349; 1.1.1打印标题&#x1f349; 1.1.1打印当前网页标题&#x1f352;1.2 窗口&#x1f349;1.2.1 获取句柄&#x1f349;1.2.2 窗口切换&#x1f349;1.2.3 窗口大小设置&…

当你点击浏览器的瞬间都发生了什么----- 网络学习笔记

计算机网络前言web 浏览器协议栈创建套接字阶段。连接阶段。断开阶段。IP模块网卡网络设备 --- 集线器、交换器和路由器集线器交换器路由器路由器的附加功能一 &#xff1a;地址转换路由器的附加功能一 &#xff1a;包过滤功能互联网内部接入网光纤接入网&#xff08;FTTH&…

JDK8 前后的 Date 日期时间 API

JDK8 前后的 Date 日期时间 API 每博一文案 师父说&#xff1a;人只要活在世界上&#xff0c;就会有很多的烦恼&#xff0c;痛苦或是快乐&#xff0c;取决于逆的内心&#xff0c;只要心里拥有温暖灿烂的阳光&#xff0c; 那么悲伤又有什么好畏惧的呢&#xff1f; 人生如行路&a…

vue学习笔记(更新中)

目录 简介 使用Vue写一个"hello&#xff0c;world" 前置准备 代码书写 MVVM模型理解 插值语法和指令语法 插值语法 指令语法 指令&#xff1a;v-bind 指令&#xff1a;v-model vue中的el和data的两种写法 数据代理 方法&#xff1a;defineProperty() 说明…

新年礼物已收到!2022 Apache IoTDB Commits 数量排名 3/364!

社区喜报&#xff01;据 The Apache Software Foundation 官方 Projects Statistics&#xff08;项目信息统计网站&#xff09;的实时数据显示&#xff0c;Apache IoTDB 在过去 12 个月&#xff08;即 2022 年度&#xff09;共发表 6829 Commits&#xff0c;排名 2022 年度 Apa…

2、Three.js开发入门与调试设置

一、添加坐标轴辅助器 AxesHelper 用于简单模拟3个坐标轴的对象. 红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴. 构造函数 AxesHelper( size : Number ) size -- (可选的) 表示代表轴的线段长度. 默认为 1. //添加坐标轴 const axesHelper new THREE.AxesHelper(5); sc…

CSS 特效之心形-彩虹-加载动画

CSS 特效之心形-彩虹-加载动画参考描述效果HTMLCSS重置元素的部分默认样式bodyli动画定义指定animationul抖动代码总汇参考 项目描述搜索引擎BingMDNMDN Web Docs 描述 项目描述Edge109.0.1518.61 (正式版本) (64 位) 效果 HTML <!DOCTYPE html> <html lang"e…

Keil C51工程转VSCode Keil Assistant开发全过程

Keil C51工程转VSCode Keil Assistant开发全过程✨这里以stc15W408AS为例。&#x1f4cc;相关篇《【开源分享】自制STC15W408AS开发板》 &#x1f4fa;编译-烧录演示&#xff1a; &#x1f4cb;转VSCODE开发环境主要原因可能代码提示以及代码跳转功能&#xff0c;或者其他。 &…

在java中操作redis

在普通项目中操作redis 1.导入maven坐标 <dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>2.8.0</version> </dependency>2.打开redis 如果redis-server闪退&#xff0c;那就先打开re…

【Spring Security】如何使用Lambda DSL配置Spring Security

本期目录1. 概述2. 新老配置风格对比Lambda风格等效的旧配置风格3. WebFlux Security4. Lambda DSL的目标1. 概述 在 Spring Security 5.2 中增强了 DSL 的功能&#xff1a;允许使用 Lambda 表达式来配置 HTTP security 。 需要注意的是&#xff1a;先前版本的配置风格仍然是…

二分查找——“C”

各位CSDN的uu们你们好呀&#xff0c;欢迎来到小雅兰的课堂&#xff0c;今天我们的内容是复习之前的内容&#xff0c;并把之前的内容的一些习题一起来做一做&#xff0c;现在&#xff0c;就让我们进入二分查找的世界吧 首先&#xff0c;我们介绍的题目就是二分查找&#xff0c;也…

module ‘tensorflow‘ has no attribute ‘Session‘

1. module ‘tensorflow‘ has no attribute ‘Session‘ 指定一个会话来运行tensorflow程序&#xff0c;在使用tensorflow1.x版本中用tensorflow.Session即可 但当我的库版本升级到2.x之后&#xff0c;就会出现标题式报错&#xff0c;于是我去查看了tensorflow库的源码&…

使用Keras搭建深度学习模型

前言 目前深度学习领域的主流框架 tensorflowkeraspytorchcaffetheanopaddlepaddle keras 代码架构 keras代码风格相比于其他框架更符合人的思维。 模型 模型的组成分为三部分&#xff1a;输入层、网络层、输出层。 输入层 输入层的作用时规定了模型输入的shape fro…

2022年智源社区年度热点推荐丨新春集锦

本文为2022年最受智源社区小伙伴喜爱的文章&#xff0c;根据文章质量和热门程度等维度计算得出。还有AI大佬的全年总结盘点总结&#xff0c;也一并推荐给你。虎年除旧&#xff0c;兔年迎新&#xff0c;藉此机会、智源编辑组全员谨祝大家新春快乐&#xff01;2022智源社区20篇最…

LINUX学习之网络配置(十一)

1.修改IP地址 使用ifconfig命令 例如要将eth0接口的IP地址更改为192.168.1.100&#xff0c;你可以使用以下命令 ifconfig eth0 192.168.1.100如果你想为IP地址设置子网掩码&#xff0c;可以使用“netmask”参数。例如&#xff0c;要将eth0接口的子网掩码设置为255.255.255.0…

[Linux]进程优先级 Linux中的环境变量

&#x1f941;作者&#xff1a; 华丞臧. &#x1f4d5;​​​​专栏&#xff1a;【LINUX】 各位读者老爷如果觉得博主写的不错&#xff0c;请诸位多多支持(点赞收藏关注)。如果有错误的地方&#xff0c;欢迎在评论区指出。 推荐一款刷题网站 &#x1f449; LeetCode刷题网站 文…

liunx centos9中安装Redis数据库,并在win10中连接redis图文详解

首先我们去Redis的官网点击download下载tar的压缩包 https://redis.io/download/#redis-downloads 用xftp将安装包上传到你的liunx服务器本地地址 解压 tar -xvf /root/redis-7.0.8.tar.gz cd进入你刚才解压的文件夹中 cd /root/redis-7.0.8 执行make进行编译 编译完成后cd进入…

普中科技MicroPython基于esp32的基础教程-03-字符串

目录 字符串 字符串的表示方式 普通字符串 原始字符串 长字符串 字符串与数字相互转换 将字符串转换为数字 将数字转换为字符串 格式化字符串 占位符% format方法 f-strings 操作字符串 字符串拼接 字符串查找 字符串替换 字符串分割 去处字符串两…

目标检测:Focal Loss

目标检测&#xff1a;Focal Loss前言Focal LossCross Entropybalanced Cross EntropyFocal Loss Definition前言 Focal loss这个idea来源于论文《Focal Loss for Dense Object Detection》,主要是为了解决正负样本、难易样本不平衡的问题。 Focal Loss Cross Entropy 在目标…

不懂Pod?不足以谈K8s

文章目录✨ 前言1. myblog改造及优化2. Pod生命周期&#x1f351; 如何编写资源 yaml&#x1f351; pod状态与生命周期3. Pod操作总结✨ 前言 在上一篇文章中&#xff0c;我们学习了 Pod 的常用设置&#xff0c;那么这篇文章咱们继续开动&#xff01; K8s落地实践之旅 —— P…