C++:哈希表的模拟实现

news2024/11/15 12:53:40

文章目录

  • 哈希
    • 哈希冲突
    • 哈希函数
  • 解决哈希冲突
    • 闭散列:
    • 开散列

哈希

在顺序结构和平衡树中,元素的Key和存储位置之间没有必然的联系,在进行查找的时候,要不断的进行比较,时间复杂度是O(N)或O(logN)

而有没有这样一种方案,可以直接不经过比较,从表中得到所需要的元素呢?直接进行获取就可以,如果存在这样的结构,那么对它而言的查找效率是很高的

插入元素

根据上面的原理,在插入元素的时候,根据插入元素的Key,找到一个可以映射到一个表中的具体位置,并进行存放

搜索元素

在对元素的Key进行计算后,就可以直接找到它被映射到了表中的哪一个位置,从而可以直接找到它在表中的位置,如果找到了就返回true

上面的这个原理,就叫做哈希,也叫做散列,而在哈希中使用的这个转换函数就叫做哈希函数,也叫做散列函数,构造出来的结构就叫做哈希表,也叫做散列表

下面用一个例子来举例:

例如数据集合有{1, 7, 6, 4, 5, 9}

那么就可以把根据一个哈希转换函数:hash(key) = key % capacity,得到一个专属于它的下标,把这个值存到下标的位置:

在这里插入图片描述
通过这样的方法就可以对元素和下标建立一种关系,在寻找的时候可以直接寻找到,在进行数据的存储和查找的过程拥有相当高的效率

但依旧有问题,如果存储的元素正好已经被存储过了呢?

哈希冲突

所谓哈希冲突,简单来说就是不同的Key值经过计算,得到了一个相同的hash值,此时再向表中填写数据就会有问题,这个过程就叫哈希冲突,也叫做哈希碰撞,那为什么会引起哈希碰撞?如何解决?

哈希函数

通常来说,引起哈希碰撞的一个原因是哈希函数有问题

常见的哈希函数定义:

  1. 直接定址法:取Key值的某个线性函数作为散列地址,例如Hash(Key)= A*Key + B
  2. 除留余数法:设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址
  3. 平方取中法
  4. 折叠法
  5. 数学分析法

哈希函数设计的越好,出现哈希冲突的可能性就越低,但无法避免哈希冲突,也就是说,哈希冲突是一定会发生的

解决哈希冲突

解决的方法通常有两种,闭散列和开散列

闭散列:

当发生哈希冲突的时候,如果哈希表没有被装满,那么就说明哈希表中肯定还有空余位置,那么就放到冲突位置的下一个位置当中去

  1. 线性探测

从发生哈希冲突的位置开始,依次向后进行探测,直到探测到了一个空位置为止

那么下面模拟实现一下线性探测的实现过程

	bool insert(const pair<K, V>& kv)
	{
		// 考虑扩容问题
		if (_n * 10 / _t.size() == 7)
		{
			size_t newsize = _t.size() * 2;
			vector<HashData<K, V>> newV;
			newV.resize(newsize);
			size_t _newn = 0;

			// 把原来的数据放到新表中 遍历一次旧表
			for (int i = 0; i < _t.size(); i++)
			{
				// 如果旧表中这个位置的值存在 就准备放到新表中
				if (_t[i]._s == EXIST)
				{
					size_t newhashi = _t[i]._kv.first % newsize;
					while (newV[newhashi]._s == EXIST)
					{
						newhashi++;
						newhashi %= newsize;
					}
					newV[newhashi]._kv = _t[i]._kv;
					newV[newhashi]._s = EXIST;
					_newn++;
				}
			}
			_t.swap(newV);
			_n = newsize;
		}
		// 正常插入逻辑
		size_t hashi = kv.first % _t.size();
		while (_t[hashi]._s == EXIST)
		{
			// 如果插入元素的位置有内容,就插入到下一个位置
			hashi++;
			hashi %= _t.size();
		}

		_t[hashi]._kv = kv;
		_t[hashi]._s = EXIST;
		_n++;

		return true;
	}

但是闭散列的缺陷是很明显的,比如当插入数据是12,22,32,42…这样的数据的时候,就会导致不停地触发哈希冲突,这样会产生堆积的效应,为了避免出现这样的问题,又提出了开散列的方案

开散列

开散列也叫做哈希桶,也叫做拉链法,原理就是把具有相同地址的Key值放到一起,每一个子集就叫做一个桶,每个桶的元素通过单链表来进行链接,每个链表的头结点在哈希表中

在这里插入图片描述
开散列中每个桶中放的都是哈希冲突的元素

namespace opened_hashing
{
	// 定义节点信息
	template<class K, class V>
	struct Node
	{
		Node(const pair<K, V>& kv)
			:_next(nullptr)
			, _kv(kv)
		{}
		Node* _next;
		pair<K, V> _kv;
	};

	template<class K, class V>
	class HashTable
	{
		typedef Node<K, V> Node;
	public:
		// 构造函数
		HashTable()
			:_n(0)
		{
			_table.resize(10);
		}

		// 析构函数
		~HashTable()
		{
			//cout << endl << "*******************" << endl;
			//cout << "destructor" << endl;
			for (int i = 0; i < _table.size(); i++)
			{
				//cout << "[" << i << "]->";
				Node* cur = _table[i];
				while (cur)
				{
					Node* next = cur->_next;
					//cout << cur->_kv.first << " ";
					delete cur;
					cur = next;
				}
				//cout << endl;
				_table[i] = nullptr;
			}
		}

		// 插入元素
		bool insert(const pair<K, V>& kv)
		{
			// 如果哈希表中有这个元素,就不插入了
			if (find(kv.first))
			{
				return false;
			}

			// 扩容问题
			if (_n == _table.size())
			{
				HashTable newtable;
				int newsize = _table.size() * 2;
				newtable._table.resize(newsize, nullptr);
				for (int i = 0; i < _table.size(); i++)
				{
					Node* cur = _table[i];
					while (cur)
					{
						Node* next = cur->_next;
						// 把哈希桶中的元素插入到新表中
						int newhashi = cur->_kv.first % newsize;
						// 头插
						cur->_next = newtable._table[newhashi];
						newtable._table[newhashi] = cur;
						cur = next;
					}
					_table[i] = nullptr;
				}
				_table.swap(newtable._table);
			}

			// 先找到在哈希表中的位置
			size_t hashi = kv.first % _table.size();

			// 把节点插进去
			Node* newnode = new Node(kv);
			newnode->_next = _table[hashi];
			_table[hashi] = newnode;
			_n++;

			return true;
		}

		Node* find(const K& Key)
		{
			// 先找到它所在的桶
			int hashi = Key % _table.size();

			// 在它所在桶里面找数据
			Node* cur = _table[hashi];
			while (cur)
			{
				if (cur->_kv.first == Key)
				{
					return cur;
				}
				cur = cur->_next;
			}
			return nullptr;
		}

		void print()
		{
			for (int i = 0; i < _table.size(); i++)
			{
				cout << i << "->";
				Node* cur = _table[i];
				while (cur)
				{
					cout << cur->_kv.first << " ";
					cur = cur->_next;
				}
				cout << endl;
			}
			cout << endl;
		}
	private:
		vector<Node*> _table;
		size_t _n;
	};
}

上面的实现看似没有问题,实际上依旧有问题,如果要传入的数据是string类,那么在比较的过程中会出现错误,因此要写一个仿函数用以处理这些情况

在这里插入图片描述
在这里插入图片描述
这里利用版本模板中的特化进行处理即可,处理细节比较巧妙

	template<class T>
	struct _Convert
	{
		T& operator()(const T& key)
		{
			return key;
		}
	};
	
	template<>
	struct _Convert<string>
	{
		size_t& operator()(const string& key)
		{
			size_t sum = 0;
			for (auto e : key)
			{
				sum += e * 31;
			}
			return sum;
		}
	};

那么下一步就要进行对于哈希表的封装了,详情见模拟实现篇章

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

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

相关文章

探索零信任架构的基础知识

根据普华永道 2023 年的一份报告&#xff0c;36% 的 CISO 已开始实施零信任组件。另外 25% 的人表示他们计划在未来几年内开始零信任之旅。 显然&#xff0c;“零信任”不仅仅是一个流行词&#xff1b;而是一个流行语。相反&#xff0c;它代表了世界对待网络安全方式的彻底转变…

Java-final

【1】修饰变量&#xff1b; 1.public class Test { 2. //这是一个main方法&#xff0c;是程序的入口&#xff1a; 3. public static void main(String[] args) { 4. //第1种情况&#xff1a; 5. //final修饰一个变量&#xff0c;变量的值不可以改变&#…

Yolov8部署——vs2019遇到的问题

Yolov8部署——vs2019遇到的问题 问题一&#xff1a; 默认库"LIBCMT"与其他库的使用冲突 解决方法&#xff1a;选择自己的项目右键属性——c/c——代码生成——运行库&#xff08;多线程&#xff08;/MT&#xff09; 问题二&#xff1a; 文件包含在偏移0x18处开始…

水库大坝安全监测系统守护水利工程安全的坚实后盾

WX-WY1 随着社会经济的发展和科技的进步&#xff0c;水利工程的安全问题越来越受到人们的关注。水库大坝作为水利工程的重要组成部分&#xff0c;其安全状况直接关系到周边地区人民的生命财产安全和生态环境。因此&#xff0c;建立一个高效、可靠的水库大坝安全监测系统至关重要…

苹果手机内嵌h5如何禁止全局弹性效果

简单模拟一个场景&#xff0c;这是一个商城的商品分类页面&#xff0c;是一个左右布局&#xff0c;左面是所有的分类&#xff0c;右面是展示这个分类的商品&#xff0c;这里为了简单就只写一个demo了。 <!DOCTYPE html> <html lang"en"><head><…

求臻医学受邀参加第三届基因检测行业发展高峰论坛并斩获三项荣誉

2023年11月18日&#xff0c;备受瞩目的第三届基因检测行业发展高峰论坛暨年度评选活动颁奖典礼在广州圆满召开。作为基因检测领域的佼佼者&#xff0c;求臻医学应邀参加了此次高峰论坛并斩获了基因检测行业年度风云企业奖、基因检测行业最具投资价值奖、基因检测行业最佳产品奖…

使用npm发布自己的组件库

在日常开发中&#xff0c;我们习惯性的会封装一些个性化的组件以适配各种业务场景&#xff0c;突发奇想能不能建一个自己的组件库&#xff0c;今后在各种业务里可以自由下载安装自己的组件。 一. 项目搭建 首先直接使用vue-cli创建一个vue2版本的项目&#xff0c;并下载好ele…

Idear 中签出git项目分支为灰色

--签出git上的项目 git clone git项目地址 --查看目录 $ dir --查看分支 $ git branch -a --签出分支 $ git checkout origin/v1.0 签出后&#xff0c;使用idear打开项目&#xff0c;项目关联git信息

PaaS、 IaaS 和 SaaS 的区别

我感觉我有点捂了 iaas&#xff0c;paas&#xff0c;和saas的区别&#xff0c;以及他们啥意思了 简单说就是&#xff0c;一个公司有很多项目&#xff0c;要管理这些项目&#xff0c;每个项目都有很多组成部分需要管理的地方&#xff0c;例如&#xff0c;存储代码&#xff0c;例…

cmake+OpenCV4.8.0+contrib4.8.0+cuda 12.2编译踩坑

cmakeOpenCV4.8.0contrib4.8.0cuda 12.2编译踩坑 准备工具 cmake &#xff08;去官网下载&#xff09;OpenCV 我下载的是官网发布最新的稳定版本对应的源码&#xff0c;官网目前是4.8.0&#xff0c;github下一个&#xff08;连不上的可以网上找找资源或者科学上网&#xff09…

PHM对复杂控制系统的状态监控及故障诊断

背景 该型号复杂控制系统是由7台各种车辆组成的复杂电子、机械复合系统&#xff0c;这些系统通过数据总线连接在一起&#xff0c;总线数据中既有控制指令数据也有执行响应数据或BIT数据&#xff0c;这些数据可以作为系统健康状态评估或故障诊断的依据&#xff0c;然而在以往类…

【机器学习基础】K-Means聚类算法

&#x1f680;个人主页&#xff1a;为梦而生~ 关注我一起学习吧&#xff01; &#x1f4a1;专栏&#xff1a;机器学习 欢迎订阅&#xff01;相对完整的机器学习基础教学&#xff01; ⭐特别提醒&#xff1a;针对机器学习&#xff0c;特别开始专栏&#xff1a;机器学习python实战…

外汇天眼:每周都能赢奖金?

最近&#xff0c;有不少外汇天眼的用户询问天眼客服&#xff0c;每周举办的外汇天眼模拟比赛是真的能拿到奖金吗&#xff1f;答案是&#xff1a;是的&#xff01;表现优秀者可瓜分350美金&#xff0c;如果周周参加&#xff0c;周周获得名次&#xff0c;那这个奖金也是能叠加获得…

Re50:读论文 Large Language Models Struggle to Learn Long-Tail Knowledge

诸神缄默不语-个人CSDN博文目录 诸神缄默不语的论文阅读笔记和分类 论文名称&#xff1a;Large Language Models Struggle to Learn Long-Tail Knowledge ArXiv网址&#xff1a;https://arxiv.org/abs/2211.08411 官方GitHub项目&#xff08;代码和实体&#xff09;&#xf…

GCC 学习

GCC Resource Center for GCC Internalshttps://www.cse.iitb.ac.in/grc/这是个不错资料网站&#xff0c;有兴趣的可以了解下

2023年【A特种设备相关管理(电梯)】考试报名及A特种设备相关管理(电梯)考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 A特种设备相关管理&#xff08;电梯&#xff09;考试报名参考答案及A特种设备相关管理&#xff08;电梯&#xff09;考试试题解析是安全生产模拟考试一点通题库老师及A特种设备相关管理&#xff08;电梯&#xff09;操…

软件数字签名是什么?软件数字签名有什么作用?

在当今互联网时代&#xff0c;网络安全威胁日益增加&#xff0c;恶意软件层出不穷&#xff0c;为了防止下载到恶意软件&#xff0c;用户在下载软件时都会确认其是安全可信的。由此&#xff0c;企业需要证明其发布的软件真实可信且未被篡改&#xff0c;如何证明这一点呢&#xf…

数据资产入表规划演示(无形资产路线)

数据“入表”有利于企业盘活数据资产、数据资产的交易定价等&#xff0c;通过数据资产“入表”可以加快数据要素市场化配置&#xff0c;为下一步全国数据市场要素市场建立提供基础支撑。数据资产入表&#xff0c;可以拆解为三步&#xff0c;第一步是入表形成原始资产&#xff0…

修改el-radio-group样式,自定义单选组件

修改el-radio-group样式,自定义单选组件 自定义组件 MyRadioGroup.vue <template><div class"btnsBox"><el-radio-group v-model"activeIndex" change"handleClick"><el-radio-buttonv-for"(item, index) in list&qu…

工作电压范围,转换速率高,相位补偿等特性的双运算放大器芯片D4510的描述

D4510是一块双运算放大器&#xff0c;具有较宽的工作电压范围&#xff0c;转换速率高&#xff0c;相位补偿等特性。电路能在低电源电压下:工作,电源电压范围:双电源为1V-3.5V和单电源电压为2V~7V。 主要特点&#xff1a; ● 低电压工作 ● 转换速率高 ● 动态输…