【C++哈希表】哈希碰撞,线性探测,二次探测 ,荷载因子,闭散列的实现及string需要特化

news2024/11/26 7:50:41

目录

1.哈希概念

2.哈希碰撞

3.解决哈希冲突

4.哈希表闭散列实现 

        框架: 

         4.3插入


1.哈希概念

  • 线性表以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。线性表查找时间复杂度为O(N),平衡树中为树的高度,即O(logN)搜索的效率取决于搜索过程中元素的比较次数

假如理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。 如果构造一种存储结构,通过某种函数(哈希)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。

  • 插入元素

                根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放

                 计算方法:关键码(数据)%数组的容量的元素个数(除留余数法)

  • 搜索元素

                对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比 较,若关键码相等,则搜索成功

该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表 (Hash Table)(或者称散列表)

插入579 ,查询任意元素的时间复杂度都是O(1)

 2.哈希碰撞

当插入1,11,21,31,41,按计算方法取模的结果都是1 ,不同关键码通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞

  • 把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。

1. 直接定制法:取关键字的某个线性函数为散列地址:Hash(Key)= 直接把关键码转化哈希地址 优点:简单、均匀缺点:需要事先知道关键字的分布情况 使用场景:适合查找比较小且连续的情况 

  • 如果数据不集中那么会有大量的空间浪费

 2. 除留余数法:设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函 数:Hash(key) = key% p(p<=m)m是容量(通常直接使用m就行),将关键码转换成哈希地址

 3.解决哈希冲突

        解决哈希冲突两种常见的方法是:闭散列开散列

3.1闭散列

闭散列:也叫开放定址法当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置那么可以把key存放到冲突位置中的“下一个” 空位置中去(思想)

3.1.线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止(实际方法)

            size_t start = hash(kv.first) % _tables.size();
			size_t i = 0;
			size_t index = start;
			// 线性探测
			while (_tables[index]._status == EXIST)
			{
				i++;
				index =start+ i;
				index %= _tables.size();
			}

3.2.二次探测 线性探测的缺陷是产生冲突的数据堆积在一块(效率会变低),这与其找下一个空位置有关系,因为找空位置的方式就是挨着往后逐个去找,因此二次探测是为了避免该问题;不在一次一次加了,加二次方,如果算出来的哈希地址大于容量,就取模就好像又走了一遍

size_t start = hash(kv.first) % _tables.size();
			size_t i = 0;
			size_t index = start;
			// 二次探测
			while (_tables[index]._status == EXIST)
			{
				i++;
				index =start + i * i;
				index %= _tables.size();
			}

3.3荷载因子 

哈希表什么情况下进行扩容?如何扩容? 

哈希表的荷载因子a:      a=插入的元素/哈希表的容量;这个值a需限制在0.7-0.8之下,达到就扩容;如果不扩容哈希碰撞的可能性就非常大,效率下降。

4.哈希表闭散列实现 

框架: 

    //状态,防止元素被删除后哈希表查询不到一些位置
	enum Status
	{
		EXIST,
		EMPTY,
		DELETE,
	};

	template<class T, class V>
	struct HashData
	{
		pair<T, V> _kv;
		Status _status = EMPTY;
	};

	//仿函数,字符串需要特殊处理
	template<class K>
	struct Hash
	// 特化
	template<>
	struct Hash<string>
	
	template<class T, class V,class Func=Hash<T>>
	class HashTable
	{
	public:
        //接口函数
		bool Erase(const T& key)
		HashData<T, V>* Find(const T& key)
		bool Insert(const pair<T, V>& kv)

	private:
		vector<HashData<T, V>> _tables;
        //插入的元素个数
		size_t _n=0;
	};

 4.1.查询

  1. 查询逻辑:key先取模得到哈希地址,按照哈希地址找,如果不是那么说明,出现过哈希碰撞,找“下一个”位置,直到找到这个数或者找到“下一个”空位置说明没有这个元素
        HashData<T, V>* Find(const T& key)
		{
			if (_tables.size() == 0)
			{
				return nullptr;
			}
            //可以先不管这个仿函数,就是取里面的值
			Func hash;
			size_t start = hash(key)% _tables.size();
			size_t i = 0;
			size_t index = start;
			while (_tables[index]._status != EMPTY)
			{
				if (_tables[index]._kv.first == key && _tables[index]._status == EXIST)
				{
					return &_tables[index];
				}
				i++;
				index =start + i * i;
				index %= _tables.size();
			}
			return nullptr;
		}

4.2删除

  1. 删除查询的位置,有就把HashData指针的成员_status改为删除(_DELETE)标记一下,没有就删除失败返回false
bool Erase(const T& key)
		{
			HashData<T, V>* ret = Find(key);
			if (ret == nullptr)
			{
				return false;
			}
			else
			{
				--_n;
				ret->_status = DELETE;
				return true;
			}
		}

 4.3插入

  1. 插入逻辑:先用Find查询是否有这个元素,有就插入失败,再判断是否需要扩容,插入就好;
bool Insert(const pair<T, V>& kv)
		{
			HashData<T, V>* ret = Find(kv.first);
			if (ret)
			{
				return false;
			}
			// 荷载因子到0.7,就扩容
			// 荷载因子越小,冲突概率越低,效率越高,空间浪费越多
			// 荷载因子越大,冲突概率越高,效率越低,空间浪费越少
			if (_tables.size() == 0 || _n * 10 / _tables.size() >= 7)
			{
				//扩容
				size_t newHashSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				HashTable<T, V> newHT;
				newHT._tables.resize(newHashSize);
				for (size_t i = 0; i < _tables.size(); i++)
				{
					if (_tables[i]._status == EXIST)
					{
						newHT.Insert(_tables[i]._kv);
					}
				}
				_tables.swap(newHT._tables);
			}

			Func hash;
			size_t start = hash(kv.first) % _tables.size();
			size_t i = 0;
			size_t index = start;
			// 线性探测 or 二次探测
			while (_tables[index]._status == EXIST)
			{
				i++;
				index =start + i * i;
				//index =start + i;
				index %= _tables.size();
			}
			_tables[index]._kv = kv;
			_tables[index]._status = EXIST;
			++_n;
			return true;
		}

4.3.1.荷载因子为0.7了,扩容会导致容量变大,取模:key/哈希表容量,那么这个哈希表的映射关系不符合了;只能全部重新插入了

 4.3.2字符串需要怎样插入了?

 

4.3.2.1仿函数及string的特化

  • 能转化为整形的就直接转化,string和自定义类型就需要一个特化版本
//仿函数
	template<class K>
	struct Hash
	{
		size_t operator()(const K& key)
		{
			return key;
		}
	};
	// 特化
	template<>
	struct Hash<string>
	{
		size_t operator()(const string& s)
		{
			size_t value = 0;
			for (auto e : s)
			{
				// BKDR,减少哈希冲突
				value *= 31;
				value += e;
			}
			return value;
		}
	};

 

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

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

相关文章

首届数据安全大赛初赛web

文章目录easy_nodeeasy_webeasy_phar刮刮乐my_cloudeasy_node 访问src得到源码 第一步需要伪造admin用户 可以看到都是通过jwt进行加密解密 很容易想到jwt的几种攻击方式&#xff0c;可以参考下文章https://blog.csdn.net/miuzzx/article/details/111936737 首先试下改为n…

Linux篇【4】:Git,Gitee,GitHub

在 Linux 系统中&#xff0c;如何将普通源文件和普通头文件上传到 Gitee &#xff1f; Git 是一个分布式的版本控制器&#xff0c;目前可以理解成百度云盘&#xff0c;其次&#xff0c;在 Linux 系统中&#xff0c;Git 也是一个软件/工具/指令&#xff0c;在 Windows 系统中&a…

212 - 218. MySQL索引的基本用法

1.索引的简介 1.1 索引的概念 是数据库对象&#xff0c;实现数据库快速查询 1.2 为什么使用索引 实现数据库快速查询&#xff0c;提高查询速度 1.3 索引的分类 a.普通索引 最基本的索引&#xff0c;对字段数据的类型和值没有任何限制&#xff0c;数据类型可以任意&#xff0c;…

电脑技巧:Win10自带的6个实用功能,你都会用吗

目录 1、系统自带计算器 2、Win10自带截图功能 3、 Win10自带的手写输入 4、历史剪贴板 5、定时提醒 6、自带录屏功能 1、系统自带计算器 系统里自带了一个计算器大家都知道&#xff0c;打开是这个样子。 但当你点击计算器左上角的三个横杠图标后会发现这个计算器功能非常强大。…

Python画爱心——谁能拒绝用代码敲出来会跳动的爱心呢~

还不快把这份浪漫拿走&#xff01;&#xff01;节日就快到来了&#xff0c;给Ta一个惊喜吧~ 今天给大家分享一个浪漫小技巧&#xff0c;利用Python制作一个立体会动的心动小爱心 成千上百个爱心汇成一个大爱心&#xff0c;从里到外形成一个立体状&#xff0c;给人视觉上的冲击…

phy层深入了解编码

1&#xff0c;我们知道mac层通过MDI来访问phy层的&#xff0c;那么phy层到光模块数据是怎么处理的呢。从下面这个图中可以看出基本的关系&#xff1a; serdies接口说明&#xff1a; Medium Independent Interface (MII): 介质无关接口。提供公共接口&#xff0c;屏蔽多个物理…

基于haproxy负载均衡实现lamp与apache的高可用

环境准备 IP主机名服务系统192.168.47.10node1ansiblecentos8192.168.47.20node2lampcentos8192.168.47.30node3apachecentos8192.168.47.40node4haproxycentos8 基于上一篇的文章在node3主机上部署apache 一、部署haproxy 准备主机清单组织 //创建角色 [studentserver rol…

服务器没网 利用本地机器反向隧道 设置conda代理配置环境

适用情况 S是局域网内服务器&#xff0c;无法联网。 C为本地机器&#xff0c;可以通过ssh链接服务器。 本篇文章主要为了可以让S可以借用C的网络进行conda环境配置&#xff0c;所采取的设置。 1.利用Xshell建立反向隧道 在连接设置的ssh中点击隧道&#xff0c;点击设置&#…

SpringBoot--网上商城项目(前端搭建、首页、用户登录、盐加密、登录令牌管理)

文章目录 一、项目技术点 数据表 二、构建SpringBoot项目 1、创建SpringBoot项目并配置pom 配置pom 2、application.yml的配置 3、首页访问 三、首页功能 工具类&#xff01;&#xff01;&#xff01; 首页数据绑定语法 四、用户明文登录 五、前端及数据库密码加密 …

C. String Transformation 1(图的思想)

Problem - 1384C - Codeforces 题意: 考拉有两个长度相同的字符串A和B&#xff08;|A||B|n&#xff09;&#xff0c;由前20个小写英文字母组成&#xff08;即从a到t&#xff09;。 在一步棋中&#xff0c;Koa。 (选择A的某个位置子集p1,p2,...,pk&#xff08;k≥1;1≤pi≤n;…

Codeforces Round #831 (Div. 1 + Div. 2)

A. Factorise NM 题目链接&#xff1a;Dashboard - Codeforces Round #831 (Div. 1 Div. 2) - Codeforces 样例输入&#xff1a; 3 7 2 75619样例输出&#xff1a; 2 7 47837题意&#xff1a;给定一个质数&#xff0c;让我们输出一个质数使得这两个数相加为一个合数。 分…

基于Java的飞机雷电射击游戏的设计实现(Eclipse开发)

目 录 摘 要 I Abstract II 1 引言 3 1.1 项目背景 3 1.2 电脑游戏的发展历史 3 1.3国内游戏项目研究现状 5 1.4项目主要工作 6 1.5本文组织结构 7 1.6 电脑游戏的策划 7 2 开发平台与开发技术 8 2.1 Eclipse 8 2.2 Eclipse平台 8 2.3 Java 9 2.4游戏图形界面的开发基础 11 2.5…

redis启动和简单使用

redis启动和简单使用 1.redis启动 1.1 找到redis解压的位置,在里面输入cmd回车 1.2 输入redis-server redis.conf指令,然后回车,出现如下界面 注意&#xff1a;该界面不能关闭了 1.3 再进入一次redis解压的位置 输入cmd回车 1.4 输入redis-cli指令后的结果 1.5 补充 当出现…

Flink系列文档-(YY05)-Flink编程API-多流算子

1 多流连接 connect connect连接&#xff08;DataStream,DataStream→ConnectedStreams) connect翻译成中文意为连接&#xff0c;可以将两个数据类型一样也可以类型不一样DataStream连接成一个新的ConnectedStreams。需要注意的是&#xff0c;connect方法与union方法不同&…

Hadoop高手之路3-Hadoop集群搭建

文章目录Hadoop高手之路3-Hadoop集群搭建一、集群的规划二、再准备两台虚拟机作为服务器1. 根据hadoop001克隆出hadoop002和hadoop0032. 配置hadoop002和hadoop0031) 启动hadoop002虚拟机并登录2) 配置ip地址3) 重启网络服务器&#xff0c;查看ip4) 远程连接hadoop0025) 修改主…

数据库自增ID用完了会怎么样?

有主键 如果设置了主键&#xff0c;并且一般会把主键设置成自增。 Mysql里int类型是4个字节&#xff0c;如果有符号位的话就是[-231,231-1]&#xff0c;无符号位的话最大值就是2^32-1&#xff0c;也就是4294967295。 创建一张表&#xff1a; CREATE TABLE test1 (id int(11…

人脸识别技术趋势与发展

人脸辨识 —— 引人入胜 很少有生物辨识技术能像脸部辨识那样激发我们的想象力。 同样&#xff0c;它的到来在 2020 年引发了深刻的担忧和令人惊讶的反应。 脸部辨识的工作原理 脸部辨识是使用脸部辨识或验证人的身份的过程。它根据人的脸部细节捕获、分析和比较模式。 人…

Restful风格的编程

Restful风格的编程1、 Restful简介2、查询用户以及用户详情2.1常用注解2.2查询用户详情3、处理创建请求3.1RequestBody注解3.1.1用途3.1.2语法规范3.2日期类型的处理3.3BindingResult4、用户信息修改与删除4.1用户信息修改4.2案例前端界面后端控制器1、 Restful简介 Restful比…

剑指offer(C++)-JZ69:跳台阶(算法-动态规划)

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 题目描述&#xff1a; 一只青蛙一次可以跳上1级台阶&#xff0c;也可以跳上2级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法&…

【JavaSE】类与对象(上)类是什么?对象是什么?

文章目录面向过程与面向对象认识类和对象创建类类的实例化内存分布注意事项总结面向过程与面向对象 我们说C语言是面向过程的编程语言&#xff0c;而Java是面向对象的编程语言&#xff0c;那究竟什么才是面向过程与面向对象呢&#xff1f;我们举一个例子来帮助大家理解&#x…