【C++】哈希表 ---开散列版本的实现

在这里插入图片描述

你很自由
充满了无限可能
这是很棒的事
我衷心祈祷你可以相信自己
无悔地燃烧自己的人生
-- 东野圭吾 《解忧杂货店》

开散列版本的实现

  • 1 前言
  • 2 开散列版本的实现
    • 2.1 节点设计
    • 2.2 框架搭建
    • 2.3 插入函数
    • 2.4 删除函数
    • 2.5 查找操作
    • 2.6 测试
  • Thanks♪(・ω・)ノ谢谢阅读!!!
  • 下一篇文章见!!!

1 前言

上一篇文章,我们介绍了哈希表的基本概念:
哈希表(Hash Table)是一种数据结构,它通过哈希函数将键映射到表中的一个位置来访问记录,支持快速的插入和查找操作。

我们可以通过对key值的处理快速找到目标。如果多个key出现相同的映射位置,此时就发生了哈希冲突,就要进行特殊处理:闭散列和开散列。

  1. 闭散列:也叫做开放定址法,其核心是出现哈希冲突,就从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。
  2. 开散列:又叫链地址法(开链法),其核心是每个位置是以链表结构储存,遇到哈希冲突就将数据进行头插。

在这里插入图片描述

我们已经实现了闭散列版本的哈希表,今天我们来实现开散列版本的哈希表(哈希桶)!

2 开散列版本的实现

我们先来分析一下,我们要实现哈希桶需要做些什么工作。开散列本质上是一个数组,每个位置对于了一个映射地址。开散列解决哈希冲突的本质是将多个元素以链表进行链接,方便我们进行寻找。既然使用到了链表我们可以直接使用list,但是list底层是双向循环链表,对于我们这样简单的情景大可不必这么复杂,使用简单的单向不循环链表即可,并且可以节省一半的空间!

2.1 节点设计

因为我们要实现单链表结构,肯定要来先设计一下节点:

	//节点设计
	template<class K, class V>
	struct HashNode
	{
		//储存的数据
		pair<K, V> _kv;
		//下一个节点的指针
		HashNode<K, V>* _next;

		//构造函数
		HashNode(pair<K, V> kv)
			:_kv(kv),
			_next(nullptr)
		{}
	};

节点里面使用pair来储存数据,并储存一个指向下一个节点的指针。这样就能实现链表结构

2.2 框架搭建

设计好了节点,就要进行整体框架的搭建,哈希桶的底层是一个指针数组,还需要一个变量来记录有效个数,方便检测何时扩容。我们简单实现最基本的工作:插入 , 删除和查找就可以。
需要注意的是,我们需要通过对应的哈希函数来将不同类型的数据转换为size_t类型,这样才能映射到数组中

//仿函数!
template<class K>
struct HashFunc
{
	//可以进行显示类型转换的直接转换!!!
	size_t operator()(const K& k)
	{
		return (size_t)k;
	}
};
//string不能进行直接转换,需要特化
template<>
struct HashFunc<string>
{
	//可以进行显示类型转换的直接转换!!!
	size_t operator()(const string& k)
	{
		size_t key = 0;
		for (auto s : k)
		{
			key *= 131;
			key += s;
		}
		return key;
	}
};

	//开散列的哈希表
	//       key           value      仿函数(转换为size_t)
	template<class K, class V, class Hash = HashFunc<K>>
	class HashTable
	{
	public:
		typedef HashNode<K, V> Node;
		//构造函数
		HashTable()
		{
			_table.resize(10, nullptr);
			_n = 0;
		}
		//插入数据
		bool insert(const pair<K, V> kv)
		{
		}
		//删除
		bool erase(const K& key)
		{
		}
		//查找
		Node* find(const K& key)
		{
		}
	private:
		//底层是一个指针数组
		vector<Node*> _table;
		//有效数量
		size_t _n;
		//仿函数
		Hash hs;
	};

2.3 插入函数

实现插入函数,需要进行以下步骤:

  1. 检查当前key是否存在,不存在才插入
  2. 根据负载因子检查是否需要扩容
  3. key 通过仿函数得到 hashi,找到映射位置
  4. 创建一个新节点,并将其头插到映射位置的链表中

扩容的逻辑需要注意一下:最容易想到的是遍历一遍原先的哈希表,将数据重新插入到新的哈希表中,然后释放原先的节点,这样顺畅就可以做到,但是这样其实做了多余的动作,我们不需要将原本的节点释放,直接将原本节点移动到新的哈希表中即可!

//插入数据
bool insert(const pair<K, V> kv)
{
	if ( find(kv.first) ) return false;
	//扩容
	if (_n == _table.size() * 0.7)
	{
		//直接把原本的节点移动到新的table中即可
		vector<Node*> newtable(2 * _table.size());
		//遍历整个数组
		for (int i = 0; i < _table.size(); i++)
		{
			if (_table[i])
			{
				Node* cur = _table[i];
				while (cur)
				{
					//获取数据
					Node* next = cur->_next;
					//计算新的映射
					int hashi = hs(cur->_kv.first) % newtable.size();
					//进行头插
					cur->_next = newtable[hashi];
					newtable[hashi] = cur;

					cur = next;
				}

			}
		}
		_table.swap(newtable);
	}
	//首先寻找到合适下标
	int hashi = hs(kv.first) % _table.size();
	//进行头插
	Node* newnode = new Node(kv);
	newnode->_next = _table[hashi];
	_table[hashi] = newnode;
	++_n;

	return true;
}

2.4 删除函数

删除的逻辑是根据key值找到对应的位置,在该位置的链表中检索是否有相等的数值。如果有就进行删除,否则返回false

	//删除
	bool erase(const K& key)
	{
		//根据key找到对应位置
		int hashi = hs(key) % _table.size();

		//在当前位置的链表中寻找目标
		Node* cur = _table[hashi];
		Node* prev = nullptr;
		while (cur)
		{
			if (cur->_kv.first == key)
			{
				//找到该位置
				//分类讨论情况
				--_n;
				//如果删除的是第一个
				if (prev == nullptr)
				{
					_table[hashi] = cur->_next;
				}
				//其他情况
				else
				{
					prev->_next = cur->_next;
				}
				delete cur;
				return true;
			}
			else
			{
				prev = cur;
				cur = cur->_next;
			}
		}
		return false;
	}

这样简单的删除就写好了!其实就是链表操作加上一步检索的操作。

2.5 查找操作

查找的逻辑和删除类似,根据key值找到映射位置,再在该链表中进行检索,找到返回节点指针,反之返回空指针。

	Node* find(const K& key)
	{
		//根据key找到对应位置
		int hashi = hs(key) % _table.size();

		//在当前位置的链表中寻找目标
		Node* cur = _table[hashi];
		while (cur)
		{
			if (cur->_kv.first == key)
			{
				return cur;
			}
			cur = cur->_next;
		}
		return nullptr;
	}

2.6 测试

我写好了插入,删除和查找。接下来就来测试一下:
实践是检验真理的唯一标准!

	//测试
	void test_HT1()
	{
		vector<int> arr = { 0 , 1 , 1 , 11 , 111 , 2 , 22 , 21 , 32 , 51 };
		HashTable<int, int> HT;
		for (int i = 0; i < arr.size(); i++)
		{
			HT.insert(make_pair(arr[i], arr[i]));
		}

		for (int i = 0; i < arr.size(); i++)
		{
			HT.erase(arr[i]);
		}
	}

	void test_HT2()
	{
		vector<int> arr = { 0 , 1 , 1 , 11 , 111 , 2 , 22 , 21 , 32 , 51 };
		HashTable<int, int> HT;
		for (int i = 0; i < arr.size(); i++)
		{
			HT.insert(make_pair(arr[i], arr[i]));
		}

		if (HT.find(1))
		{
			std::cout << HT.find(1)->_kv.first << ':' << HT.find(1)->_kv.second << endl;
		}
	}

	void test_HT3()
	{
		vector<string> arr = { "sort" , "hello" , "JLX" , "Hi" };
		HashTable<string, string> HT;
		for (int i = 0; i < arr.size(); i++)
		{
			HT.insert(make_pair(arr[i], arr[i]));
		}

		if (HT.find("sort"))
		{
			std::cout << HT.find("sort")->_kv.first << ':' << HT.find("sort")->_kv.second << endl;
		}
	}

}

这里我们分别测试插入删除,插入寻找,字符串的处理:
我进入调试来看看是否正常:
在这里插入图片描述
通过对监视窗口的查看,我们可以验证我们的代码正常运行的!

Thanks♪(・ω・)ノ谢谢阅读!!!

下一篇文章见!!!

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

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

相关文章

OpenCV 灰度直方图及熵的计算

目录 一、概述 1.1灰度直方图 1.1.1灰度直方图的原理 1.1.2灰度直方图的应用 1.1.3直方图的评判标准 1.2熵 二、代码实现 三、实现效果 3.1直方图显示 3.2 熵的计算 一、概述 OpenCV中的灰度直方图是一个关键的工具&#xff0c;用于分析和理解图像的灰度分布情况。直…

Excel多表格合并

我这里一共有25张表格: 所有表的表头和格式都一样,但是内容不一样: 现在我要做的是把所有表格的内容合并到一起,研究了一下发现WPS的这项功能要开会员的,本来想用代码撸出来的,但是后来想想还是找其他办法,后来找到"易用宝"这个插件,这个插件可以从如下地址下载:ht…

图像处理中的二维傅里叶变换

图像处理中的二维傅里叶变换 问题来源是对彩色图像进行压缩时得出的傅里叶系数的图像如何解释&#xff0c;导入图片&#xff0c;转化为灰度图片&#xff1a; #彩色图片一般是由RGB组成&#xff0c;其实就是3个二维数组叠加而成&#xff0c;当RGB时&#xff0c;彩色图片就会变成…

【线性代数的本质】矩阵与线性变换

线性变化要满足两点性质&#xff1a; 直线&#xff08;连续的点&#xff09;在变换后还是直线。原点不变。 假设有坐标轴&#xff08;基底&#xff09; i ^ \widehat{i} i 和 j ^ \widehat{j} j ​&#xff1a; i ^ [ 1 0 ] , j ^ [ 0 1 ] \widehat{i}\begin{bmatrix} 1 \…

【leetcode】双指针算法题

文章目录 1.算法思想2.移动零3.复写零方法一方法二 4.快乐数5.盛水最多的容器方法一&#xff08;暴力求解&#xff09;方法二&#xff08;左右指针&#xff09; 6.有效三角形的个数方法一&#xff08;暴力求解&#xff09;方法二&#xff08;左右指针&#xff09; 7.两数之和8.…

ONLYOFFICE 8.1版本震撼来袭,让办公更高效、更智能

官网链接&#xff1a; 在线PDF查看器和转换器 | ONLYOFFICE 在线办公套件 | ONLYOFFICE 随着科技的不断发展&#xff0c;办公软件已经成为现代企业提高工作效率、实现信息共享的重要工具。在我国&#xff0c;一款名为ONLYOFFICE的在线办公套件受到了越来越多企业的青睐。今天…

Prompt-Free Diffusion: Taking “Text” out of Text-to-Image Diffusion Models

CVPR2024 SHI Labshttps://arxiv.org/pdf/2305.16223https://github.com/SHI-Labs/Prompt-Free-Diffusion 问题引入 在SD模型的基础之上&#xff0c;去掉text prompt&#xff0c;使用reference image作为生成图片语义的指导&#xff0c;optional structure image作为生成图片…

深入理解【 String类】

目录 1、String类的重要性 2、常用方法 2、1 字符串构造 2、2 String对象的比较 2、3 字符串查找 2、4字符转换 数值和字符串转换&#xff1a; 大小写转化&#xff1a; 字符串转数组&#xff1a; 格式转化&#xff1a; 2、5 字符串替换 2、6字符串拆分 2、7 字符串…

知名品牌因商标痛失市场:114家直营店山寨店7000多家!

奶茶知名品牌“鹿角巷”当年红遍大江南北&#xff0c;是最早的新茶饮品牌&#xff0c;但是当年商标注册存在问题&#xff0c;被同行奶茶品牌抢占了先机&#xff0c;发声明“对大陆商标注册细则不详&#xff0c;在商标注册过程中让假店钻了法律空档”&#xff0c;最夸张的时候全…

python如何不保留小数

1、int() 向下取整&#xff08;内置函数&#xff09; n 3.75 print(int(n)) >>> 3 n 3.25 print(int(n)) >>> 3 2、round() 四舍五入&#xff08;内置函数&#xff09; n 3.75 print(round(n)) >>> 4 n 3.25 print(round(n)) >>> 3 …

JavaScript(5)——数据类型和类型检测

字符串类型String 通过单引号&#xff08; &#xff09;、双引号(" "&#xff09;或反引号&#xff08; &#xff09;都叫字符串&#xff0c;单引号和双引号本质上没有区别&#xff0c;一般使用单引号。 注意&#xff1a; 无论单引号或是双引号必须成对使用单引号和…

人工智能系列-NumPy(二)

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 链接数组 anp.array([[1,2],[3,4]]) print(第一个数组&#xff1a;) print(a) print(\n) bnp.array([[5,6],[7,8]]) print(第二个数组&#xff1a;) print(b) print(\n) print…

PHP智慧门店微信小程序系统源码

&#x1f50d;【引领未来零售新风尚】&#x1f50d; &#x1f680;升级启航&#xff0c;智慧零售新篇章&#x1f680; 告别传统门店的束缚&#xff0c;智慧门店v3微信小程序携带着前沿科技与人性化设计&#xff0c;正式启航&#xff01;这个版本不仅是对过往功能的全面优化&a…

【trition-server】运行一个pytorch的ngc镜像

ngc 提供了pytorch容器 号称是做了gpu加速的 我装的系统版本是3.8的python,但是pytorch似乎是用conda安装的3.5的: torch的python库是ls支持gpu加速是真的 英伟达的pytorch的说明书 root@a79bc3874b9d:/opt/pytorch# cat NVREADME.md PyTorch ======= PyTorch is a python …

【C++航海王:追寻罗杰的编程之路】关联式容器的底层结构——AVL树

目录 1 -> 底层结构 2 -> AVL树 2.1 -> AVL树的概念 2.2 -> AVL树节点的定义 2.3 -> AVL树的插入 2.4 -> AVL树的旋转 2.5 -> AVL树的验证 2.6 -> AVL树的性能 1 -> 底层结构 在上文中对对map/multimap/set/multiset进行了简单的介绍&…

C++基础21 二维数组及相关问题详解

这是《C算法宝典》C基础篇的第21节文章啦~ 如果你之前没有太多C基础&#xff0c;请点击&#x1f449;C基础&#xff0c;如果你C语法基础已经炉火纯青&#xff0c;则可以进阶算法&#x1f449;专栏&#xff1a;算法知识和数据结构&#x1f449;专栏&#xff1a;数据结构啦 ​ 目…

短视频父亲:成都柏煜文化传媒有限公司

短视频父亲&#xff1a;镜头背后的温情与力量 在这个信息爆炸的时代&#xff0c;短视频以其短小精悍、直观生动的特点&#xff0c;迅速占据了人们碎片化的时间&#xff0c;成为情感交流与文化传播的重要平台。而在这些纷繁复杂的短视频中&#xff0c;有一类内容尤为触动人心—…

如何让自动化测试更加灵活简洁?

简化的架构对于自动化测试和主代码一样重要。冗余和不灵活性可能会导致一些问题&#xff1a;比如 UI 中的任何更改都需要更新多个文件&#xff0c;测试可能在功能上相互重复&#xff0c;并且支持新功能可能会变成一项耗时且有挑战性的工作来适应现有测试。 页面对象模式如何理…

ELK日志系统和Filebeat采集器的学习总结

ELK是ElasticSerach、Logstash、Kina Logstash负责采集数据&#xff0c;Logstash有三个插件&#xff0c;input、filter、output&#xff0c;filter插件作用是对采集的数据进行处理&#xff0c;过滤的&#xff0c;因此filter插件可以选&#xff0c;可以不用配置。 ElasticSear…

ASUS/华硕枪神5 G533Q G733Q系列 原厂win10系统 工厂文件 带F12 ASUS Recovery恢复

华硕工厂文件恢复系统 &#xff0c;安装结束后带隐藏分区&#xff0c;一键恢复&#xff0c;以及机器所有驱动软件。 系统版本&#xff1a;Windows10 原厂系统下载网址&#xff1a;http://www.bioxt.cn 需准备一个20G以上u盘进行恢复 请注意&#xff1a;仅支持以上型号专用…