【C++ —— 认识哈希和unordered_set、unordered_map的介绍及模拟】

认识哈希和unordered_set、unordered_map的介绍及模拟

  • 哈希表基础
    • 哈希的概念
    • 哈希表的基本操作
  • 哈希冲突
    • 哈希冲突的定义
    • 哈希冲突的影响
    • 常见的哈希冲突的解决方法
  • 哈希函数
    • 哈希函数的定义
    • 哈希函数的设计原则
    • 常见的哈希函数
  • unordered系列关联式容器
  • hash模拟

哈希表基础

哈希的概念

哈希表(Hash Table),也称为散列表,是一种通过哈希函数将键值(Key) 映射到对应位置(Bucket)数据结构。哈希表使用一个数组来存储数据,通过哈希函数计算出键值的索引,插入、查找和删除操作的时间复杂度可以接近 O ( 1 ) O(1) O(1)

unordered系列的关联式容器之所以效率比较高,是因为其底层使用了哈希结构。

哈希表的基本操作

当向哈希表的结构中:

  • 插入元素
    根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放。
  • 搜索元素
    对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置
    取元素比较,若关键码相等,则搜索成功。

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


例如:数据集合{1,7,6,4,5,9};
哈希函数设置为:hash(key) = key % capacity; capacity为存储元素底层空间总的大小。

在这里插入图片描述
用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快
问题:按照上述哈希方式,向集合中插入元素44,会出现什么问题?

哈希冲突

哈希冲突的定义

哈希冲突(Hash Collision) 是指两个或多个不同的键通过哈希函数映射到同一个位置。当哈希表发生冲突时,需要使用某种机制来解决冲突,确保所有键值对能够正确存储和查找。

哈希冲突的影响

哈希冲突会导致数据存储位置重叠,增加查找时间,降低哈希表的整体性能。频繁的冲突会使哈希表的操作复杂度从 O ( 1 ) O(1) O(1)恶化为 O ( n ) O(n) O(n),影响系统性能。

常见的哈希冲突的解决方法

1. 开放地址法
开放地址法通过探测空闲位置来解决冲突。常见的探测方法有:

下面讲重点讲解线性探测二次探测分离链接法

线性探测(Linear Probing)

  • 发生冲突时,从当前位置开始,逐个检查后续位置,直到找到空位为止。
  • 优点:实现简单。
  • 缺点:可能导致“堆积”现象,即冲突连续发生。

比如上述中的场景,现在需要插入元素44,先通过哈希函数计算哈希地址,hashAddr为4,
因此44理论上应该插在该位置,但是该位置已经放了值为4的元素,即发生哈希冲突。

线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。

  • 插入

    • 通过哈希函数获取待插入元素在哈希表中的位置
    • 如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,使用线性探测找到下一个空位置,插入新元素
      在这里插入图片描述
  • 删除

    • 采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索素。 比如删除元素4,如果直接删除掉,44查找起来可能会受影响。因此线性探测采用标记的伪删除法来删除一个元素。

二次探测(Quadratic Probing)

  • 发生冲突时,从当前位置开始,以二次方为步长逐个检查后续位置(例如,1^2, 2^2, 3^2,…),直到找到空位为止。
  • 优点:减少线性探测中的堆积现象。
  • 缺点:需要处理哈希表大小和步长选择的问题。

二次探测

 线性探测的缺陷是产生冲突的数据堆积在一块,这与其找下一个空位置有关系,因为找空位置的方式就是挨着往后逐个去找,因此二次探测为了避免该问题,找下一个空位置的方法为: H i H_i Hi =( H 0 H_0 H0 + i 2 i^2 i2 )% m, 或者: H i H_i Hi = ( H O H_O HO - i 2 i^2 i2 )% m。其中: i =1,2,3., H o H_o Ho是通过散列函数Hash(x)对元素的关键码key进行计算得到的位置,m是表的大小。对于上述中如果要插入44,产生冲突,使用解决后的情况为:

在这里插入图片描述

  研究表明:当表的长度为质数且表装载因子a不超过0.5时,新的表项一定能够插入,而且任何一个位置都不会被探查两次。因此只要表中有一半的空位置,就不会存在表满的问题。在搜素时可以不考虑表装满的情况,但在插入时必须确保表的装载因子a不超过0.5,如果超出必须考虑增容。


双重哈希(Double Hashing)

  • 发生冲突时,使用第二个哈希函数计算新的步长,从当前位置开始探测空闲位置。
  • 优点:进一步减少冲突聚集。
  • 缺点:需要设计两个有效的哈希函数。

2. 链地址法
链地址法通过在每个哈希表位置存储一个链表来解决冲突。

分离链接(Separate Chaining)

  • 分离链接法通过将每个哈希表槽位维护一个链表来处理冲突。
  • 每个哈希表槽位存储一个链表,所有冲突的元素都插入该链表中。
  • 优点:简单易实现,动态调整链表长度。
  • 缺点:在最坏情况下(所有元素都落在同一个桶中),查找时间复杂度退化为 O ( n ) O(n) O(n)

在这里插入图片描述
在这里插入图片描述

自适应链表(Adaptive Chaining)

  • 根据链表长度和冲突情况,动态调整存储结构,例如在链表长度较长时将其转换为红黑树。
  • 优点:在链表较短时保持简单性,在链表较长时提高查找效率。
  • 缺点:实现较为复杂,需要维护多种数据结构。

哈希函数

哈希函数的定义

哈希函数的定义
 哈希函数(Hash Function)是将键值转换为哈希值的函数。哈希值用于确定键值在哈希表中的存储位置。一个好的哈希函数能均匀分布键,减少冲突,提高哈希表的性能。

哈希函数的设计原则

  • 均匀分布:哈希值应均匀分布在整个哈希表,避免冲突集中在某些位置。
  • 快速计算:哈希函数应高效计算,减少额外开销,提高性能。
  • 最小冲突:尽量避免相同哈希值的情况,使不同的键尽量映射到不同的位置。

常见的哈希函数

1. 除留余数法 (Modulo Division Method)
 解释:除留余数法是最简单和常用的哈希函数之一。它通过将键除以哈希表的大小并取余数来生成哈希值。这样生成的哈希值会在0到表大小-1的范围内,适合直接作为哈希表的索引。

优点:
 实现简单,计算快速。
缺点:
 如果键的分布不均匀,可能导致冲突较多。
C++代码

int modHash(int key, int table_size) 
{return key % table_size;
}

2. 平方取中法 (Mid-Square Method)
 解释:平方取中法是将键值平方,然后取中间几位作为哈希值。这样做的优点是可以通过平方操作增加哈希值的复杂性,从而使得哈希值更均匀地分布。

优点:
 中间位数的选择可以减少键值的偏差,减少冲突。
缺点:
 适用于键值长度较短的情况。
C++代码:

int midSquareHash(int key, int table_size) 
{int squared = key * key;// 假设key是一个4位数,取中间2位int mid_digits = (squared / 100) % 100;return mid_digits % table_size;
}

3. 直接定址法 (Direct Addressing Method)
 解释:直接定址法将键值直接映射到哈希表的索引上,适用于键值范围较小且较为密集的情况。键值直接作为索引使用,不需要额外计算。

优点:
 查找、插入和删除操作的时间复杂度都是O(1)。
 实现非常简单。
缺点:
 需要保证键值的范围在哈希表大小之内,否则会导致索引越界。
 键值范围大且稀疏时会浪费内存。
C++代码:

int multiplicativeHash(int key, int table_size) 
{double A = 0.618033;  // (sqrt(5) - 1) / 2 的值double frac = key * A - int(key * A);return int(table_size * frac);
}

unordered系列关联式容器

unordered系列关联式容器 包含 unordered_mapunordered_set,在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时效率可达到 l o g 2 N log_2 N log2N,即最差情况下需要比较红黑树的高度次,当树中的节点非常多时,查询效率也不理想。最好的查询是,进行很少的比较次数就能够将元素找到,因此在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同。

unordered_map/unordered_set 和 map/set 的区别

特性map/setunordered_map/unordered_set
内部实现红黑树(自平衡二叉搜索树)哈希表
元素顺序有序(按键值排序)无序(按哈希值存储)
时间复杂度O(log n)平均 O(1),最坏 O(n)
迭代顺序按键值顺序无固定顺序
内存占用较高(存储节点信息)较低(可能有冲突链表开销)
使用场景需要有序存储和范围查询需要快速查找、插入和删除

简要总结

  • map 和 set:适用于需要保持元素有序的场景,例如有序字典、按键值范围查询。
  • unordered_map 和 unordered_set:适用于需要快速查找、插入和删除的场景,且对元素顺序没有要求。

unordered_map/unordered_set 的接口可以查询cplusplus.com使用用法与之前介绍的相似。

hash模拟

开放地址法hash

#pragma once
#include <vector>template<class K>
struct HashFunc
{size_t operator()(const K& key){return (size_t)key;}
};//特化
template<>
struct HashFunc<string>
{size_t operator()(const string& key){size_t hash = 0;for (auto ch : key){hash *= 131;hash += ch;}return hash;}
};namespace open_address
{enum State{EMPTY,EXIST,DELETE};template<class K, class V>struct HashData{pair<K, V> _kv;State _state = EMPTY;};struct StringHashFunc{size_t operator()(const string& k){size_t hash = 0;for (auto& e : k){hash *= 131;hash += e;}return hash;}};template<class K, class V, class Hash = HashFunc<K>>class HashTable{public:HashTable(){_tables.resize(10);}bool Insert(const pair<K, V>& kv){if (Find(kv.first))return false;//扩容if (_n * 10 / _tables.size() >= 7){HashTable<K, V, Hash> newHT;newHT._tables.reserve(_tables.size() * 2);//旧表插入到新表for (size_t i = 0; i < _tables.size(); i++){if (_tables[i]._state == EXIST){newHT.Insert(_tables[i]._kv);}}_tables.swap(newHT._tables);}Hash hs;size_t hashi = hs(kv.first) % _tables.size();//线性探测while (_tables[hashi]._state == EXIST){hashi++;hashi %= _tables.size();}_tables[hashi]._kv = kv;_tables[hashi]._state = EXIST;_n++;return true;}HashData<K, V>* Find(const K& k){Hash hs;size_t hashi = hs(k) % _tables.size();//线性探测while (_tables[hashi]._state != EMPTY){if (_tables[hashi]._state == EXIST && _tables[hashi]._kv.first == k){return &_tables[hashi];}hashi++;hashi %= _tables.size();}return nullptr;}bool Erase(const K& k){HashData<K, V>* ret = Find(k);if (ret == nullptr){return false;}else{ret->_state = DELETE;_n--;return true;}}private:vector<HashData<K, V>> _tables;size_t _n = 0;		//有效数据个数};

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

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

相关文章

AI大模型大厂面试真题:「2024大厂大模型技术岗内部面试题+答案」

AI大模型岗的大厂门槛又降低了&#xff01;实在太缺人了&#xff0c;大模型岗位真的强烈建议各位多投提前批&#xff0c;▶️众所周知&#xff0c;2025届秋招提前批已经打响&#xff0c;&#x1f64b;在这里真心建议大家6月7月一定要多投提前批&#xff01; &#x1f4bb;我们…

树莓派_Opencv学习笔记23:模版样本匹配

今日继续学习树莓派4B 4G&#xff1a;&#xff08;Raspberry Pi&#xff0c;简称RPi或RasPi&#xff09; 本人所用树莓派4B 装载的系统与版本如下: 版本可用命令 (lsb_release -a) 查询: ​ Opencv 版本是4.5.1&#xff1a; ​ Python 版本3.7.3&#xff1a; 今日学习Opencv样本…

【日常记录】【插件】Typed.js:用于创建打字效果的 JavaScript 库

文章目录 1. 引言2. 安装3. 基本使用参考链接 1. 引言 Typed.js是一个用于创建打字效果的 JavaScript 库。这个效果就是 chatgpt、百度的文心一言等其他的大模型&#xff0c;回复用户的问题的时候的效果 typed-js 官网typed 案例 2. 安装 CDN方式 这俩都可以&#xff0c;还有其…

18.springboot整合swagger

springboot整合swagger 引入依赖 <!--swagger--> <dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version> </dependency> <dependency><groupId&…

Postman设置全部请求都携带请求头,Postman如何一次性设置请求头、不需要一个请求一个请求去添加请求头

文章目录 一、问题描述二、解决办法三、应用场景 一、问题描述 现在我有 n 个接口测试&#xff0c;其中 n 个都需要携带一致的请求头&#xff08;其实一般都是携带 JWT 令牌&#xff09;&#xff0c;怎么办&#xff1f;我要一个一个写&#xff1f;如图&#xff1a; 二、解决办…

添加sidecar容器并输出日志

添加一个sidecar容器(使用busybox 镜像)到已有的pod 11-factor-app中,确保sidecar容器能够输出/var/log/11-factor-app.log的信息,使用volume挂载/var/log目录,确保sidecar能访问11-factor-app.log 文件 # 准备工作 创建一个 pod 11-factor-appapiVersion: v1 kind: Pod metada…

PDF管理器和查看器PdfDing

什么是 PdfDing &#xff1f; PdfDing 是一款自托管 PDF 管理器和查看器&#xff0c;可在多种设备上提供无缝用户体验。它设计精简、速度快&#xff0c;并且易于通过 Docker 设置。 功能特点 在多种设备上无缝基于浏览器的 PDF 查看使用标签整理 PDF干净且响应迅速的用户界面暗…

VulnHub:colddbox easy

靶机下载地址 信息收集 主机发现 攻击机网段192.168.31.0/24&#xff0c;扫描同网段存活主机。 nmap 192.168.31.0/24 -Pn -T4 发现靶机&#xff0c;IP为192.168.31.176。 端口扫描 扫描靶机开放端口。 nmap 192.168.31.176 -A -p- -T4 开放了80,4512端口&#xff0c;注…

Prometheus安装部署

文章目录 1.Prometheus(普罗米修斯)安装部署1.1部署环境准备1.2部署prometheus1.3主机数据展示 2.Grafana安装部署2.1部署Grafana2.2配置Grafana数据源2.2配置Grafana仪表板 3.AlertManager安装部署3.1部署alertmanager3.2告警邮件发送配置3.3测试邮件告警效果3.4自定义邮件告警…

ModuleNotFoundError: No module named ‘py3langid‘ 以及如何将包安在虚拟环境下

前提&#xff1a;已经安装过改包&#xff08;pip install py3langid&#xff09;&#xff0c;但仍报错 原因&#xff1a;安装在其他目录下了 解决办法&#xff1a; 1、再次在终端输入pip install py3langid 显示安装位置 Requirement already satisfied: py3langid in c:\…

【ESP8684————固件烧录说明、调试记录(1)】

环境&#xff1a; 硬件&#xff1a;ESP8684 模块 芯片&#xff1a;ESP8684-MINI-1U 固件&#xff1a;ESP32-C2-4MB-AT-V3.3.0.0 固件烧录及烧录说明整理&#xff1a;固件及烧录说明整理 一、调试笔记&#xff1a; 1&#xff09;关于ESP8684模块&#xff08;ATCWMODE&#xf…

Jmeter三种方式获取数组中多个数据并将其当做下个接口参数入参【附带JSON提取器和CSV格式化】

目录 一、传统方式-JOSN提取器获取接口返回值 1、接口调用获取返回值 2、添加JSON提取器 3、调试程序查看结果 4、添加循环控制器 5、设置count计数器 6、添加请求 7、执行请求 二、CSV参数化 1、将结果写入后置处理程序 2、设置循环处理器 3、添加CSV文件 4、设置…

智税集成2.0生成凭证

:::info &#x1f4a1; 整体业务流程 从A9服务器中取数&#xff0c;生成列表数据&#xff0c;写入到对方oracle数据库中。 ::: 项目关键点 1.连接数据库 左连接连接本地SQLserver数据库、右连接要链接A9开票服务器的数据库然后设想用SQLserver 自带的外部连接来连接oracle数据…

【资料分享】2024第三届钉钉杯大学生大数据挑战赛B题思路解析+双语言代码

2024钉钉杯大学生大数据挑战赛&#xff0c;B题解题思路和双语言代码分享&#xff0c;资料预览&#xff1a;

解密阿里大神写的天书般的Tree工具类,轻松搞定树结构!

首发公众号&#xff1a;赵侠客 一、引言 最近公司新进了不少新人&#xff0c;包括一些来自阿里、网易等大型企业的资深工程师。我们组的一位新同事是阿里来的专家&#xff0c;我在CR&#xff08;Code Review, 简称CR&#xff09;时看到了他编写的一个关于树操作的工具类&#…

操作系统:进程1

一.进程 1.什么是进程 一个进程创建&#xff0c;他会生成几块&#xff1a; 代码段&#xff1a;进程执行的程序代码数据段&#xff1a;全局变量&#xff0c;静态变量&#xff0c;在进程生命周期中是动态可变的堆&#xff1a;动态分配的内存区域&#xff0c;malloc、calloc、real…

html实现酷炫美观的可视化大屏(十种风格示例,附源码)

文章目录 完整效果演示1.蓝色流线风的可视化大屏1.1 大屏效果1.2 大屏代码1.3 大屏下载 2.地图模块风的可视化大屏2.1 大屏效果2.2 大屏代码2.3 大屏下载 3.科技轮动风的可视化大屏3.1 大屏效果3.2 大屏代码3.3 大屏下载 4.蓝色海洋风的可视化大屏4.1 大屏效果4.2 大屏代码4.3 …

快速介绍git(Linux)

git 1、安装2、版本控制3、git vs gitee&&GitHub(git故事)4、git的操作 1、安装 很简单&#xff0c;直接 sudo yum install -y git2、版本控制 故事介绍&#xff1a;你是一个大学生&#xff0c;你上课需要交一分实验报告&#xff0c;教你的老师比较负责&#xff0c;…

手把手教你集成GraphRag.Net:打造智能图谱搜索系统

在人工智能和大数据发展的背景下&#xff0c;我们常常需要在项目中实现知识图谱的应用&#xff0c;以便快速、准确地检索和使用信息。 今天&#xff0c;我将向大家详细介绍如何在一个新的.NET项目中集成GraphRag.Net&#xff0c;这是一个参考GraphRag实现的.NET版本&#xff0c…

Linux_make/Makefile的理解

1.make是一个命令&#xff0c;makefile是一个文件, 依赖关系和依赖方法. a.快速使用一下 i.创建一个Makefile文件(首字母也可以小写) b.依赖关系和依赖方法 i.依赖关系: 我为什么要帮你? mybin:mytest.c ii.依赖方法: 怎么帮? gcc -o mybin mytest.c make之前要注意先创建…