C++实现开散列/链地址法

news2024/11/19 10:26:27

前言

解决哈希冲突的方法有闭散列和开散列,上篇博客C++实现闭散列已经讲解完了闭散列的实现方式
本篇博客实现开散列/连地址法的哈希表

在这里插入图片描述

文章目录

  • 前言
  • 一. 开散列
  • 二. 开散列实现
    • (1). 结构
    • (2). 插入
    • (3). 查找
    • (4). 删除
    • (5). 析构函数
  • 三. 完整代码
  • 结束语

一. 开散列

开散列又叫连地址法(开链法),首先对关键码集合使用哈希函数计算哈希地址,具有相同地址的关键码归于同一子集,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各个链表的头结点存储在哈希表中
可以看作线性表挂着一个个链表

哈希函数依然使用除留余数法
Hash(key) = key % capacity
在这里插入图片描述

开散列中的每个桶中放的都是发生哈希冲突的元素

二. 开散列实现

我们同样使用除留余数法的哈希函数实现线性表

(1). 结构

这次线性表中存储的不是哈希表结点,而是结点的指针。

//桶结点
template<class K,class V>
struct HashNode
{
	pair<K, V>_kv;//值域
	HashNode*_next;//指针域
	
	//构造函数
	HashNode(const pair<K,V>kv)
		:_kv(kv)
		,_next(nullptr)
	{}
};

template<class K,class V>
class HashBucket
{
	typedef HashNode<K, V> Node;
private:
	vector<Node*> _tables;//线性表
	size_t _n = 0;//大小
};

(2). 插入

插入的逻辑

  1. 先通过哈希函数,映射到哈希地址
  2. 使用头插的方式,链接新结点

在这里插入图片描述

代码如下:

//插入
	bool Insert(const pair<K, V>&kv)
	{
		//寻址
		size_t hashi = kv.first%_tables.size();
		
		//头插
		Node*newNode = new Node(kv);
		newNode->_next = _tables[hashi];
		_tables[hashi] = newNode;

		_n++;

		return true;//插入成功
	}

这段代码存在两个问题

  1. 最开始哈希表的大小为0,那么就会出现除零异常
  2. 如果哈希表的容量满了,那么是否需要扩容

在开散列中,桶的个数是一定的,随着元素的不断插入,每个桶元素的个数不断增多,极端情况下,可能会导致一个桶中链表结点非常多,会影响哈希表的性能,因此在一定条件下需要对哈希表进行扩容
开散列最好的情况时:每个哈希桶中刚好挂一个结点,再继续插入元素时,每一次都会发生哈希冲突,因此,在元素个数刚好等于桶的个数时,可以对哈希表进行扩容

扩容:我们可以重新开一个vector,然后将原vector的数据取出,重新映射到新vector中,最后交换一下vector即可
代码如下:

	//插入
	bool Insert(const pair<K, V>&kv)
	{
		//扩容
		if (_n==_tables.size())
		{
			size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;

			vector<Node*>newTables;
			newTables.resize(newSize, nullptr);

			for (auto &cur : _tables)
			{
				//将原先的结点直接放到新表中
				while (cur)
				{
					Node*next = cur->_next;

					//寻新址
					size_t hashi = cur->_kv.first%newSize;

					//链接
					cur->_next = newTables[hashi];
					newTables[hashi] = cur;

					cur = next;
				}
			}

			//交换vector
			_tables.swap(newTables);
		}

		//寻址
		size_t hashi = kv.first%_tables.size();
		
		//头插
		Node*newNode = new Node(kv);
		newNode->_next = _tables[hashi];
		_tables[hashi] = newNode;

		_n++;

		return true;//插入成功
	}

(3). 查找

查找的逻辑是:

  1. 根据哈希函数求得哈希地址
  2. 遍历链表,查找所求值

代码如下:
注意,要判断是否当前为空表,避免出现除零异常

	//查找
	Node* Find(const K&key)
	{
		if (_tables.size() == 0)
			return false;

		//寻址
		size_t hashi = key % _tables.size();

		Node*cur = _tables[hashi];
		while (cur)
		{
			if (cur->_kv.first == key)
			{
				return cur;
			}
			cur = cur->_next;
		}

		return nullptr;
	}

(4). 删除

删除的逻辑如下:

  1. 根据哈希函数求得哈希地址
  2. 遍历链表,删除结点
    删除结点有两种情况:1.删除的结点为首结点 2. 删除的结点为中间结点

代码如下:

	//删除
	bool Erase(const K&key)
	{
		if (_tables.size() == 0)
			return false;

		size_t hashi = key % _tables.size();
		Node*cur = _tables[hashi];
		Node*prev = nullptr;
		while (cur)
		{
			if (cur->_kv.first == key)
			{
				//说明是第一个节点
				if (prev == nullptr)
				{
					_tables[hashi] = cur->_next;
				}
				else
				{
					prev->_next = cur->_next;
				}
				delete cur;
				_n--;
				return true;
			}
			else
			{
				prev = cur;
				cur = cur->_next;
			}
		}

		return false;
	}

(5). 析构函数

因为Node结点是我们自己new申请的,所以我们需要在析构函数中,将其释放
代码如下:

	//析构函数
	~HashBucket()
	{
		for (auto&cur : _tables)
		{
			while (cur)
			{
				Node*next = cur->_next;
				delete cur;
				cur = next;
			}

			cur = nullptr;
		}
	}

其实就是遍历线性表,然后将插入的结点挨个释放。

三. 完整代码

#pragma once

//桶结点
template<class K,class V>
struct HashNode
{
	pair<K, V>_kv;//值域
	HashNode*_next;//指针域

	HashNode(const pair<K,V>kv)
		:_kv(kv)
		,_next(nullptr)
	{}
};

template<class K,class V>
class HashBucket
{
	typedef HashNode<K, V> Node;
public:
	//析构函数
	~HashBucket()
	{
		for (auto&cur : _tables)
		{
			while (cur)
			{
				Node*next = cur->_next;
				delete cur;
				cur = next;
			}

			cur = nullptr;
		}
	}

	//插入
	bool Insert(const pair<K, V>&kv)
	{
		if (Find(kv.first))
			return false;

		//扩容
		if (_n==_tables.size())
		{
			size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;

			vector<Node*>newTables;
			newTables.resize(newSize, nullptr);

			for (auto &cur : _tables)
			{
				//将原先的结点直接放到新表中
				while (cur)
				{
					Node*next = cur->_next;

					//寻新址
					size_t hashi = cur->_kv.first%newSize;

					//链接
					cur->_next = newTables[hashi];
					newTables[hashi] = cur;

					cur = next;
				}
			}

			//交换vector
			_tables.swap(newTables);
		}

		//寻址
		size_t hashi = kv.first%_tables.size();
		
		//头插
		Node*newNode = new Node(kv);
		newNode->_next = _tables[hashi];
		_tables[hashi] = newNode;

		_n++;

		return true;//插入成功
	}

	//查找
	Node* Find(const K&key)
	{
		if (_tables.size() == 0)
			return false;

		//寻址
		size_t hashi = key % _tables.size();

		Node*cur = _tables[hashi];
		while (cur)
		{
			if (cur->_kv.first == key)
			{
				return cur;
			}
			cur = cur->_next;
		}

		return nullptr;
	}

	//删除
	bool Erase(const K&key)
	{
		if (_tables.size() == 0)
			return false;

		size_t hashi = key % _tables.size();
		Node*cur = _tables[hashi];
		Node*prev = nullptr;
		while (cur)
		{
			if (cur->_kv.first == key)
			{
				//说明是第一个节点
				if (prev == nullptr)
				{
					_tables[hashi] = cur->_next;
				}
				else
				{
					prev->_next = cur->_next;
				}
				delete cur;
				_n--;
				return true;
			}
			else
			{
				prev = cur;
				cur = cur->_next;
			}
		}

		return false;
	}

private:
	vector<Node*> _tables;//线性表
	size_t _n = 0;//大小
};

结束语

本篇内容到此就结束了,感谢你的阅读!

如果有补充或者纠正的地方,欢迎评论区补充,纠错。如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。
在这里插入图片描述

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

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

相关文章

RocketMQ JVM/OS配置与订阅关系一致

一、JVM/OS配置 1 、JVM选项​ 推荐使用最新发布的 JDK 版本。通过设置相同的 Xms 和 Xmx 值来防止 JVM 调整堆大小以获得更好的性能。生产环境 JVM 配置如下所示&#xff1a; -server -Xms8g -Xmx8g -Xmn4g 当 JVM 是默认 8 字节对齐&#xff0c;建议配置最大堆内存不要超过…

AI加持的必应,为什么还赢不了谷歌?

“少年屠龙”的故事&#xff0c;似乎还有些遥远。 即使有新必应的加成&#xff0c;微软浏览器Edge在全球市场的占有率依然不高。据Statcounter数据显示&#xff0c;2023年4月&#xff0c;Edge的市场占有率仅为4.97%。提升的速度似乎也不太理想&#xff0c;4月份的数据只比一年…

《Netty》从零开始学netty源码(五十九)之ServerBootstrapAcceptor

ServerBootstrapAcceptor 前面初始化channel的过程中向pipeline中添加了一个channelHandler&#xff0c;即ServerBootstrapAcceptor&#xff0c;它的作用主要是将worker组的channel进行注册&#xff0c;它的数据结构如下&#xff1a; 它的属性主要是通过ServerBootstrap启动类…

07-通过RocketMQ和Redis实现用户动态提醒

1、用户动态表 CREATE TABLE `t_user_moments` (`id` bigint(12) unsigned NOT NULL AUTO_INCREMENT COMMENT 主键id,`user_id` bigint(12) DEFAULT NULL COMMENT 用户id,`user_type` int(8) DEFAULT NULL COMMENT 动态类型:0视频 1直播 2专栏动态,`contend_id` bigint(12) D…

五个不错的样机素材网站推荐

设计师完成作品后&#xff0c;为了更好地展示作品&#xff0c;通常会将设计作品应用到真实的样机素材模板中。 本文推荐五个不错的样机素材网站&#xff0c;希望对你有所帮助。 1.即时设计 即时设计是一款「专业UI设计工具」&#xff0c;不受平台限制&#xff0c;打开浏览器…

使用JUnit进行单元测试、JUL日志系统配置、Mybatis日志系统配置、Lombok开启日志

文章目录 使用JUnit进行单元测试原因测试断言工具类案例一&#xff1a;错误冒泡排序案例二&#xff1a;从数据库获取数据 Before注解After JUL日志系统使用JUL日志修改日志的打印级别文件处理器控制打印格式日志设置过滤器 Properties配置文件编写日志配置文件使用Lombok快速开…

短信验证码

阿里云短信 1.1 介绍 短信服务&#xff08;Short Message Service&#xff09;由阿里云提供短信平台&#xff0c;调用API即可发送验证码、通知类和营销类短信&#xff1b;国内验证短信秒级触达&#xff0c;到达率最高可达99%。 官方网站&#xff1a;https://www.aliyun.com/…

getchar、putchar以及输入缓冲区

目录 1.getchar和putchar的文献 1.1关于getchar的文献: 1.2关于putchar的文献 1.3返回值问题 2.从键盘中输入一个字符 2.1原理&#x1f4a8; &#x1f6a9;2.2如何理解: ❗理解1: ❗理解2&#xff1a; 2.3关于程序如何结束 3.输入密码 3.1调用一次getchar读取相当于…

开发笔记之:文件读取值溢出bug分析(JAVA版)

&#xff08;1&#xff09;引言 以下是Java读取数据文件&#xff08;FileInputStream&#xff09;的代码&#xff1a; /*** 按双字读取* param fis 文件输入流* param isBigEndian 是否大头&#xff08;字节序&#xff09;* return 双字值 | <code>-1</cod…

vue2 axios请求后端数组数据 并展示

目录 1 vue加依赖 --> 终端中install 2 main.js 引入依赖 3 components -> 组件中 如 HelloWorld.vue 中 3.1 中定义数组 并接收数据赋值给数组 3.2 el表格 接收数据数据 并展示出来 4 效果 1 vue加依赖 --> 终端中install npm i axios vue-axiosnpm i element…

Cesium教程(一):Cesium的下载和安装

目录 1、Cesium简介 2、Cesium下载和安装 2.1 下载方式1 2.2 下载方式2 3、Cesium测试 4、我的第一个Ceisum程序《HelloCesium》 1、Cesium简介 首先进入Cesium官网 Cesium 是 3D 地理空间平台Cesium 是软件应用程序的开放平台&#xff0c;旨在释放 3D 数据的力量。用于…

RocketMQ的安装讲解详细手册--------以及启动Broker启动找不到类问题

RocketMQ的安装 1.RocketMQ安装 1.1下载RocKetMQ 下载地址&#xff1a;https://rocketmq.apache.org/release-notes/2017/12/13/4.2.0 下载解压后 bin:可执行文件目录 confidence&#xff1a;配置文件目录 lib:依赖库&#xff0c;是一些jar包 1.1配置ROCKETMQ_HOME 解压…

前端工程化配置

前端工程化配置指南 如何构建一个工程化的前端库&#xff0c;并结合 Github Actions&#xff0c;自动发布到 Github 和 NPM 的整个详细流程。 示例 我们经常看到像 Vue、React 这些流行的开源项目有很多配置文件&#xff0c;他们是干什么用的&#xff1f;他们的 Commit、Releas…

如何利用Jmeter从0到1做一次完整的压测

压测&#xff0c;在很多项目中都有应用&#xff0c;是测试小伙伴必备的一项基本技能&#xff0c;刚好最近接手了一个小游戏的压测任务&#xff0c;一轮压测下来&#xff0c;颇有收获&#xff0c;赶紧记录下来&#xff0c;与大家分享一下&#xff0c;希望大家能少踩坑。 一、压测…

Selenium自动化测试设计模式 —— PO模式

前言&#xff1a; 在python自动化过程中&#xff0c;Selenium自动化测试中有一个名字常常被提及PageObject&#xff08;思想与面向对象的特性相同&#xff09;&#xff0c;通过PO模式可以大大提高测试用例的维护效率。 不了解po设计模式的可自行百度 面向对象的特性&#xf…

Class 05 - 逻辑运算符and,or,not 和 条件语句 if

Class 05 - 逻辑运算符and,or,not 和 条件语句 if 逻辑运算符和条件语句逻辑运算符 and , or , notand 运算符“&”OR 运算符 “|”not 运算符“&#xff01; 案例运用 and , or , notsubset() 筛选数据AND 实例OR 实例NOT 实例混合使用实例 条件语句 ifif 语句else语句els…

五分钟搞懂Web UI自动化测试中的POM设计模式。

今天&#xff0c;我们来聊聊Web UI自动化测试中的POM设计模式。 为什么要用POM设计模式 前期&#xff0c;我们学会了使用PythonSelenium编写Web UI自动化测试线性脚本 线性脚本&#xff08;以快递100网站登录举栗&#xff09;&#xff1a; 使用以上代码&#xff0c;最基础最…

RabbitMQ五种消息模型

文章目录 1.简单消息队列模型2.Work工作队列模型3.发布订阅模型3.1.Fanout广播3.2.Direct路由3.3.Topics通配符 RabbitMQ官方文档 RabbitMQ 提供了5种常用消息模型。但是其实3、4、5这三种都属于订阅模型&#xff0c;只不过进行路由的方式不同。 1.简单消息队列模型 简单消息队…

Midjourney提示词资源、使用技巧、艺术家资源网站收录

为了帮助艺术家们使用Midjourney更专业、准确地创作更完美的艺术作品&#xff0c;我们收录了一些Midjourney提示词资源分享、提示词书写技巧、相关专业工具&#xff0c;同时还有一些相关艺术家资源帮艺术家们找到创作灵感。有很多是社区内资深玩家分享的云文档&#xff0c;资源…

【机器视觉3】双目立体视觉模型

双目立体视觉模型 简单模型一般模型 简单模型 假设两个摄像机平行放置于同一高度、光轴平行、成像平面重合、焦距相同、左右图像每一行y坐标方向、大小相同&#xff0c;如下图所示&#xff1a; 由左右成像平面上的点、目标物点、焦距、摄像机中心基线距离的几何关系可以得到&…