【数据结构】哈希桶

目录

前言:

开散列(哈希桶)

开散列的概念

哈希桶的模拟实现

 整体框架

查找

插入

删除

析构函数


前言:

闭散列线性探测缺点:一旦发生哈希冲突,所有的产生哈希冲突的数据连续存储在一块区域,容易产生数据"堆积",即:不同关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降低,并且闭散列导致空间利用率低,因此本文探索采用开散列(哈希桶)的数据结构从而避免数据 "堆积" ;

开散列(哈希桶)

开散列的概念

开散列法又叫链地址法(拉链法),首先对关键码集合用哈希函数计算哈希地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链

接起来,各链表的头结点存储在哈希表中;

哈希桶的模拟实现

 整体框架

template<class K, class V>
struct HashNode
{
	HashNode<K, V>* _next;
	pair<K, V> _kv;
    
    //开辟结点时需要结点的构造函数
	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*> _tables;
	size_t _n;//记录哈希表中实际存放的数据个数
};

查找

思路:

首先根据键值key使用哈希函数计算哈希地址,确定待查找的数据的位置;

其次遍历桶中数据,查找到返回数据所在结点的指针,查找不到返回空指针;

Node* Find(const K& key)
{
	size_t hashi = key % _tables.size();
	Node* cur = _tables[hashi];
	while (cur != nullptr)
	{
		if ((cur->_kv).first == key)
		{
			return cur;
		}
		cur = cur->_next;
	}
	return nullptr;
}

插入

开散列最优情形:每个哈希桶中刚好挂一个节点,再继续插入元素时,每一次都会发生哈希冲突,因此,在元素个数刚好等于桶的个数时,考虑哈希表扩容

  • 查看哈希表中是否存在该键值的键值对,若已存在则插入失败;
  • 当负载因子增加到1时,进行扩容操作(即创建一个新的哈希表,该哈希表的大小为原哈希表的两倍,然后遍历原哈希表,将原哈希表中的结点插入到新哈希表,最后将原哈希表与新哈希表交换即可);
  • 将结点插入哈希桶(头插);
  • 哈希表中的记录实际存储的数据个数自增1;
//构造函数
HashTable(size_t n = 10)
{
	_tables.resize(n, nullptr);
	_n = 0;
}
bool Insert(const pair<K, V>& kv)
{
	//若键值对中的键值key已存在,则插入失败
	Node* ret = Find(kv.first);
	if (ret != nullptr)
	{
		return false;
	}

	//控制负载因子为1,即哈希表中实际存放的数据个数与哈希表长度长度相等
	if (_n == _tables.size())
	{
		//新表扩容到旧表的两倍
		vector<Node*> _newtables(_tables.size() * 2);
		//遍历旧表,取旧表结点头插到新表
		for (size_t i = 0; i < _tables.size(); i++)
		{
			Node* cur = _tables[i];
			while (cur != nullptr)
			{
				Node* nextnode = cur->_next;
		
				size_t hashi = cur->_kv.first % _newtables.size();
				cur->_next = _newtables[hashi];
				_newtables[hashi] = cur;

				cur = nextnode;
			}
			_tables[i] = nullptr;
		}

		//交换新表与旧表
		_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;
}

删除

bool Erase(const K& key)
{
	//确定待删除数据的位置
	size_t hashi = key % _tables.size();
	Node* cur = _tables[hashi];
	Node* prev = nullptr;
	while (cur != nullptr)
	{
		if ((cur->_kv).first == key)
		{
			//删除
			if (prev != nullptr)
			{
				prev->_next = cur->_next;
			}
			else
			{
				_tables[hashi] = cur->_next;
			}
			delete cur;

			--_n;
			return true;
		}
		else
		{
			prev = cur;
			cur = cur->_next;
		}
	}
	return false;
}

析构函数

//析构函数
~HashTable()
{
	for (size_t i = 0; i < _tables.size(); i++)
	{
		Node* cur = _tables[i];
		while (cur != nullptr)
		{
			Node* next = cur->_next;
			delete cur;
			cur = next;
		}
		_tables[i] = nullptr;
	}
}

当哈希表中的HashNode存储string时,string类型的数据不支持取模运算,若采用运算符重载,需要更改库中的string,除留余数法如何建立string类型数据与存储位置的映射关系吗?

解决方案:

  1. 首先将string类型转换为整型,建立string类型与整型的映射关系(采用仿函数实现);
  2. 其次转换后的整型值与存储位置建立映射关系(模版参数增加1个接收仿函数);

思考:任意类型的值如何转换为整型值 ?

  • 本身为整型家族的成员,则可以通过强制类型转换可以转化为整型;
  • 对于string类型,可以取每个字符的ASCII码值,逐个相加且每相加一次乘以权重31;
  • 对于其他类型,按数据类型各自的特征自定义仿函数实现转换为整型;
template<class K>
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};

//string类型使用模版的特化
template<>
struct HashFunc<string>
{
	size_t operator()(const string& key)
	{
		size_t hash = 0;
		for (auto e : key)
		{
			hash += e;
			hash *= 31;//BKDR字符串哈希算法,累乘因子为31
		}
		return hash;
	}
};
template<class K, class V, class Hash = HashFunc<K>>
class HashTable
{
	typedef HashNode<K, V> Node;
public:
	//...
private:
	vector<Node*> _tables;
	size_t _n;//记录哈希表中实际存放的数据个数
};

欢迎大家批评指正,博主会持续输出优质内容,谢谢各位观众老爷观看,码字画图不易,希望大家给个一键三连支持~ 你的支持是我创作的不竭动力~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

算法-栈操作

1047. 删除字符串中的所有相邻重复项 - 力扣&#xff08;LeetCode&#xff09; class Solution { public:string removeDuplicates(string s) {string stack;for(char& ch:s){if(stack.size()>0&&chstack.back()){stack.pop_back();}else{stack.push_back(ch);}…

2.搭建增长模型-福格行为模型

福格行为模型 Bmat B为行动 m是动机 a是能力 t是触发 mat三者是同时出现的 比如连续签到30天&#xff0c;才送1天会员&#xff0c;这明摆着欺负人&#xff0c;用户难有积极性 但是签到即可或者会员1天&#xff0c;连续30天送30天&#xff0c;这样用户每天都会积极的来签到&…

学习交流论坛-测试报告

&#x1f31f; 欢迎来到 我的博客&#xff01; &#x1f308; &#x1f4a1; 探索未知, 分享知识 !&#x1f4ab; 本文目录 1. 项目描述2. 测试计划2.1 功能测试2.1.1 测试环境2.1.2 编写测试用例2.1.3 部分手工功能测试 2.2 自动化测试2.2.1 注册页面2.2.2 登录页面2.2.3 博客…

链表(2) ---- 完整版

目录 一 . 前言二 . 头文件声明三 . 代码思绪1. 查找函数的实现2. 在指定位置之前插入3. 在指定位置之后插入4. 删除指定位置的结点5. 删除指定位置之后的结点6. 销毁链表 四 . 完整代码五 . 总结 正文开始 一 . 前言 补充说明&#xff1a; 1、链式机构在逻辑上是连续的&#…

【算法】前缀和

快乐的流畅&#xff1a;个人主页 个人专栏&#xff1a;《算法神殿》《数据结构世界》《进击的C》 远方有一堆篝火&#xff0c;在为久候之人燃烧&#xff01; 文章目录 引言一、一维前缀和二、二维前缀和三、寻找数值的中心下标四、除自身以外数组的乘积五、和为k的子数组六、和…

【Github】sync fork后,意外关闭之前提交分支的pr申请 + 找回被关闭的pr请求分支中的文件

【Github】sync fork后&#xff0c;意外关闭之前提交分支的pr申请 找回被关闭的pr请求分支中的文件 写在最前面原因解析提交pr&#xff0c;pr是什么&#xff1f;rebase 或者 merge 命令 找到分支中被删除的文件找到被关闭的提交请求pr方法1&#xff1a;在公共仓库被关闭的pr中…

LeetCode in Python 69. Sqrt(x) (x的平方根)

求x的平方根&#xff0c;第一想法可能是遍历0&#xff5e;x&#xff0c;求其平方&#xff0c;找到或且但其时间复杂度为O(n)&#xff0c;或是想到遍历0&#xff5e;M即可&#xff0c;其中M x // 2&#xff0c;将时间复杂度降至O()。本文利用二分思想&#xff0c;给出一种时间复…

博睿数据亮相GOPS全球运维大会,Bonree ONE 2024春季正式版发布!

2024年4月25日&#xff0c;博睿数据 Bonree ONE 2024 春季正式版焕新发布。同时&#xff0c;博睿数据AIOps首席专家兼产品总监贺安辉携核心产品新一代一体化智能可观测平台 Bonree ONE 亮相第二十二届 GOPS 全球运维大会深圳站。 Bonree ONE 2024 春季版产品重点升级数据采集、…

网上打印资料多少钱一张?网上打印价格是多少?

在数字化时代&#xff0c;网上打印服务正逐渐成为一种便捷、高效的打印解决方案。对于许多需要打印资料的用户来说&#xff0c;了解网上打印的价格和服务质量至关重要。那么&#xff0c;网上打印资料到底多少钱一张&#xff1f;网上打印价格又是如何呢&#xff1f;今天&#xf…

视频号下载小程序:轻松获取视频号视频

在数字化时代&#xff0c;短视频已成为人们日常生活中不可或缺的一部分。为了满足用户随时随地观看视频的需求&#xff0c;视频号小程序应运而生。本文将详细介绍视频号小程序的下载方法、功能特点以及使用技巧&#xff0c;帮助您更好地享受短视频带来的乐趣。 一、视频号小程…

C++ 之 string类 详细讲解

喜欢的人有点难追怎么办 那就直接拉黑 七个女生在一起是七仙女&#xff0c;那七个男生在一起是什么&#xff1f; 葫芦七兄弟 目录 一、为什么要学习string类 二、标准库中的string类 1.string类 2.string类的常用接口说明 2.1 string类对象的常见构造 2.2 string类对…

Vivado-OOC

OOC⇒Out-of-Context 在Vivado中&#xff0c;对于顶层设计&#xff0c;vivado使用自顶向下的全局&#xff08;global&#xff09;综合&#xff0c;将顶层文件下的所有模块都进行综合&#xff0c;但是在实际设计过程中&#xff0c;顶层设计会被多次修改和综合&#xff0c;但是有…

AI语音侵权第一案:配音演员获赔25万元,如何保护你的声音资产?

会议之眼 快讯 近日&#xff0c;北京互联网法院对全国首例AI声音侵权案作出一审宣判&#xff0c;引发了社会对AI技术与个人权益保护关系的广泛讨论。 原告殷某&#xff0c;一名配音师&#xff0c;发现自己的声音被AI化后在“魔音工坊”APP上出售&#xff0c;遂将运营主体等五…

Linux的学习之路:20、进程信号(2)

摘要 本章讲一下进程信号的阻塞信号和捕捉信号和可重入函数 目录 摘要 一、阻塞信号 1、阻塞信号 2、信号集操作函数 二、捕捉信号 1、内核如何实现信号的捕捉 2、代码实演 三、可重入函数 一、阻塞信号 1、阻塞信号 实际执行信号的处理动作称为信号递达(Delivery) …

MyBatis源码之前言—JDBC编码存在的问题和Mybatis的介绍

MyBatis源码之前言—JDBC编码存在的问题和Mybatis的介绍 为了方便操作&#xff0c;我们在sjdwz_test数据库下建立一张表&#xff1a; CREATE TABLE t_student (id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键,name varchar(255) DEFAULT NULL COMMENT 名字,age int(255…

Web端Webrtc,SIP,RTSP/RTMP,硬件端,MCU/SFU融合视频会议系统方案分析

Web端视频融合&#xff0c;会议互通已经是视频会议应用的大趋势&#xff0c;一是目前企业有大量的老视频会议硬件设&#xff0c;二新业务又需要Web端支持视频会议监控直播需求&#xff0c;迫切需要一个融合对接的方案&#xff0c;即能把老的设备用起来&#xff0c;又能对接新的…

【每日刷题】Day22

【每日刷题】Day22 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. 1669. 合并两个链表 - 力扣&#xff08;LeetCode&#xff09; 2. 11. 盛最多水的容器 - 力扣&#…

分类算法——ROC曲线与AUC指标(九)

知道TPR与FPR TPRTP/(TP FN) 所有真实类别为1的样本中&#xff0c;预测类别为1的比例 FPR FP/(FP TN) 所有真实类别为0的样本中&#xff0c;预测类别为1的比例 ROC曲线 ROC曲线的横轴就是FPRate&#xff0c;纵轴就是TPRate&#xff0c;当二者相等时&#xff0c;表示的意义…

Linux 内核设备树 ranges属性

今天有人问了我一下ranges属性&#xff0c;找了相关资料确认后&#xff0c;记录一下&#xff1a; 参考资料链接&#xff1a;让你完全理解linux内核设备树ranges属性地址转换 - vkang - 博客园 (cnblogs.com) ranges属性定义如下&#xff1a; ranges < local_address pa…

webpack面试题(持续汇总ing。。。)

webpack的编译过程 初始化 此阶段&#xff0c;webpack会将CLI参数、配置文件、默认配置进行融合&#xff0c;形成一个最终的配置对象。对配置的处理过程是依托一个第三方库 yargs 完成的。此阶段相对比较简单&#xff0c;主要是为接下来的编译阶段做必要的准备目前&#xff0c;…