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

文章目录

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

哈希

在顺序结构和平衡树中,元素的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.mfbz.cn/a/176276.html

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

相关文章

数据库基本操作-----数据库用户管理和授权

一、数据库用户管理 1&#xff0e;新建用户 CREATE USER 用户名来源地址 [IDENTIFIED BY [PASSWORD] 密码];‘用户名’&#xff1a;指定将创建的用户名 ‘来源地址’&#xff1a;指定新创建的用户可在哪些主机上登录&#xff0c;可使用IP地址、网段、主机名的形式&#xff0c…

linux下流媒体压力测试工具的使用

前言 因为领导要求做linux的推拉流时服务器压力测试&#xff0c;于是在网上找了找。一顿操作下来&#xff0c;发现很多软件盗用一款名为srs-bench的开源软件。 该代码仓库有详细的使用说明&#xff0c;而且可以在issues中找到可能会遇到的问题的解决办法 需要下载该仓库的源…

C# Onnx 百度PaddleSeg发布的实时人像抠图PP-MattingV2

目录 效果 模型信息 项目 代码 下载 效果 图片源自网络侵删 模型信息 Inputs ------------------------- name&#xff1a;img tensor&#xff1a;Float[1, 3, 480, 640] --------------------------------------------------------------- Outputs -----------------…

ZLMediaKit安装配置和推拉流

一、ZLMediaKit 库简介 ZLMediaKit 是一个基于 C11 的高性能运营级流媒体服务框架 官方写的项目特点&#xff1a; 基于 C11 开发&#xff0c;避免使用裸指针&#xff0c;代码稳定可靠&#xff0c;性能优越。 支持多种协议(RTSP/RTMP/HLS/HTTP-FLV/Websocket-FLV/GB28181/MP…

栈的生长方向不总是向下

据我了解&#xff0c;栈的生长方向向下&#xff0c;内存地址由高到低 测试 windows下&#xff1a; 符合上述情况 测试Linux下&#xff1a; 由此可见&#xff0c;栈在不同操作系统环境下&#xff0c;生长方向不总是向下

【Python】Vscode解决Python中制表符和空格混用导致的缩进问题

【Python】Vscode解决Python中制表符和空格混用导致的缩进问题 文章目录 【Python】Vscode解决Python中制表符和空格混用导致的缩进问题1. 问题来源2. 解决Reference 1. 问题来源 在python中使用缩进来进行代码块的分区&#xff0c;通常来说python的一个缩进包含4个空格&#…

不存在类型变量 A, T 的实例,使 Collector<T, A, List<T>> 符合 Supplier<R>

报错信息 原因: 不存在类型变量 A, T 的实例&#xff0c;使 Collector<T, A, List<\T>> 符合 Supplier<\R> 来源 测试Stream流的map方法&#xff0c;做算法习惯基本类型定义数组。 map方法:Stream API的一部分。允许以一种声明式的方式处理数据&#xff0c…

nodejs搭建本地服务

前端开发时想自己有个本地服务如下操作直接上干货 1.在桌面上直接在powerShell 输入命令行 npm install -g express-generator 然后 npm install -g express 然后新建一个例如server的文件夹 在powerShell执行 express myStudy -e 端口号默认是3000 直接在地址栏输入 http://…

Windows 7 连接 Windows 10 共享打印机,Windows 无法连接打印机,操作失败,错误为0x0000011b 的终极解决办法

Windows 7 连接 Windows 10 共享打印机出现错误 0x000001b&#xff0c;建议不要通过卸载Windows10系统的KB5005565安全更新来解决该问题&#xff08;犹如削足适履&#xff09;&#xff0c;正确的处理方法是手工添加一个本地打印机&#xff0c;本方法是安全可靠的。本文详述了该…

枚举 蓝桥oj DNA序列修正

题目详情&#xff1a; 简单翻译&#xff1a; 主要思路&#xff1a; 1 本题采用贪心思路&#xff0c;要使调整次数最少&#xff0c;就是尽量交换两个碱基对&#xff0c;而不是单个替换&#xff0c;因为本题已经说明只能每个碱基对只能交换一次&#xff0c;所以不考虑A与B交换再…

NC65 修改元数据字段长度

NC65 修改元数据字段长度&#xff0c;执行下面sql&#xff0c;执行完后需要重启NC服务才生效。 --属性 update md_property set attrlength 200 where name fphm and classidece96dd8-bdf8-4db3-a112-9d2f636d388f ;--列 update md_column set columnlength 200 where tab…

远程命令执行漏洞原理,以及防护绕过方式

一、背景 RCE(Remote Command /Code Execute) 远程代码执行漏洞 通过PHP代码注入、Java代码注入等方式连接程序中预留的后门或接口从而进行远程命令执行&#xff0c;达到对服务器的控制。 为什么会出现远程代码执行漏洞呢&#xff1f; Web应用有时需要调用执行一些系统命令函数…

YOLOv5 环境搭建

YOLOv5 环境搭建 flyfish 环境 Ubuntu20.04 驱动、CUDA Toolkit、cuDNN、PyTorch版本对应 1 NVIDIA驱动安装 在[附加驱动界]面安装驱动时&#xff0c;需要输入安全密码&#xff0c;需要记下&#xff0c;后面还需要输入这个密码 重启之后有的机器会出现 perform mok manage…

C2039 编译clr工程报错

在编译clr工程的时候出现错误 错误提示如下 出现上述情况的代码文件 crl头文件VideoPlayerCLRDLL.h 被crl引用的头文件PlayerEnterPort.h 在上述情况下&#xff0c;编译clr工程会编译opengl_player.h头文件中的内容&#xff0c;但在clr工程中不认识std::mutex&#xff0c…

设计模式篇---外观模式

文章目录 概念结构实例总结 概念 外观模式&#xff1a;为子系统中的一组接口提供一个统一的入口。外观模式定义了一个高层接口&#xff0c;这个接口使得这一子系统更加容易使用。 外观模式引入了一个新的外观类&#xff0c;它为多个业务类的调用提供了一个统一的入口。主要优点…

Leetcode—8.字符串转换整数(atoi)【中等】

2023每日刷题&#xff08;三十七&#xff09; Leetcode—8.字符串转换整数&#xff08;atoi&#xff09; 算法思想 参考k神的题解 实现代码 int myAtoi(char* s) {int len strlen(s);if(len 0) {return 0;}int boundary INT_MAX / 10;int i 0, ans 0;while(s[i] ) …

Java异常处理机制

Java异常处理机制 一、异常概述与异常体系结构异常概述Error示例代码&#xff1a;Exception示例代码&#xff1a;异常体系结构Error和Exception的区别:异常分类检查异常非检查异常Why:为什么有非检查异常&#xff1f;Where:非检查异常有哪些&#xff1f;Exception异常划分运行时…

京东商品详情数据接口(JD.item_get)

京东商品详情数据接口是京东开放平台提供的一种API接口&#xff0c;通过调用该接口&#xff0c;开发者可以获取京东商品的标题、价格、库存、月销量、总销量、库存、详情描述、图片等详细信息。该接口的主要作用是帮助开发者获取商品的详细数据&#xff0c;从而更好地了解和分析…

【顺序表的应用-通讯录的实现】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 一、顺序表的应用 1. 基于动态顺序表实现通讯录 1、功能要求 2、代码实现 二、通讯录的代码实现 1.通讯录的底层结构(顺序表) (1)思路展示 (2)底层代码实现(顺序表…

灾备建设中,跨主机集群恢复技术应用

在介绍跨主机集群恢复之前&#xff0c;要了解到虚拟化主机集群是什么&#xff1f; 虚拟化主机集群是一种把一组主机组合起来形成一个整体&#xff0c;向用户提供资源方式&#xff08;计算存储、存储资源、网络资源&#xff09;的技术。 虚拟化集群具有以下特性&#xff1a; …