C++——哈希

目录

1.undered系列容器

1.1 undered_map

1.1.1 undered_map特点介绍

1.1.2 undered_map接口介绍

1.2 undered_set

2.底层结构

2.1 哈希概念

2.2 哈希冲突

2.3 哈希函数

2.3.1 哈希函数设计原则:

2.3.2 常见哈希函数

1.直接定值法

2.除留余数法

3.平方取中法

4.折叠法

5.随机数法

6.数学分析法

2.4 哈希冲突的解决

2.4.1 闭散列

1.线性探测

2.二次探测

2.4.2 开散列


1.undered系列容器

C++STL库中undered系列容器有undered_map与undered_set等,它们也就是我们常说的哈希表。

1.1 undered_map

1.1.1 undered_map特点介绍

undered_map有以下特点:

  • undered_map是存储键值对<key,value>的关联式容器。
  • 与map不同,undered_map存储的key没有顺序,但同样是通过key索引得到value。
  • undered_map中的键值key不可修改且唯一。
  • undered_map支持[ ]访问,下标为键值key。如果key不存在,那么会将key进行插入,对应的value为默认值。
  • undered_map的通过key访问value的效率比map快

1.1.2 undered_map接口介绍

                         函数声明                                    功能介绍
undered_map构造不同格式的undered_map对象
bool empty()const检测undered_map是否为空
size_t size()const获取undered_map有效元素的个数
begin返回undered_map第一个元素的迭代器
end返回undered_map最后一个元素的下一个位置的迭代器
cbegin返回undered_map第一个元素的const迭代器
cend返回undered_map最后一个元素的下一个位置的const迭代器
operator[]返回key对应的value
iterator find(const K& key)返回key在哈希桶的位置
size_t count(const K& key)返回键值为key的元素的个数
insert

向容器中插入键值对

erase删除容器中的键值对

1.2 undered_set

undered_set与undered_map类似,只不过存储的只有键值key,但实际底层仍是储存键值对<key,value>,且key=value。它不支持[]访问。其他参考undered_map。

2.底层结构

undered系列容器的效率高的原因是使用了哈希结构。

2.1 哈希概念

平衡树与顺序结构的搜索方式都是通过比较key进行查找,顺序查找的时间复杂度为O(n),平衡树查找的时间复杂度为O(logn)。

这里我们提出一种理想的查找方式,也就是哈希(散列)方法:

我们提供一个函数HashFunc来建立key与value一一映射的联系,可以类比数学中的自变量x与因变量y,它们通过函数HashFunc来建立联系,这样就可以一次通过key来得到value,时间复杂度也就成了O(1)。

这里的HashFunc称为哈希(散列)函数,构造出来的结果称为哈希(散列)表。

例如对于数据{1,7,4,5,9}:

2.2 哈希冲突

不同key通过相同哈希函数得到相同的哈希地址,这种情况称为哈希冲突或者哈希碰撞。

以上图为例:

我们发现不同的键值4与14通过相同的哈希函数得到了相同的4号哈希地址,这就是哈希冲突。

2.3 哈希函数

发生哈希冲突的一个原因就是选用的哈希函数不够合理。选取的哈希函数越精妙,哈希冲突的概率就越小,但哈希冲突是不可避免的。

2.3.1 哈希函数设计原则:

  • 哈希函数的定义域必须包含所有需要储存的键值key。如果哈希表有n个地址空间,那么哈希函数的值域为0~n-1。
  • 哈希函数计算出的地址能均匀分布在整个地址空间中。
  • 哈希函数应该比较简单。

2.3.2 常见哈希函数

1.直接定值法

取关键字的某个线性函数作为散列地址:Hash(key)=key*n+m。

优点:简单,均匀。

缺点:需要事先知道关键字的分布情况。

适用场景:适合查找小且集中的情况。

2.除留余数法

如果哈希表中的地址数为n,取一个不大于n但最接近n或者等于n的质数作为除数。

按照哈希函数:Hash(key)=key%p(p<=key)得到哈希地址。

3.平方取中法

假设关键字为1234,对它平方就是1522756,抽取中间的227作为哈希地址

适用于不知道关键字分布情况,而位数不是很大的场景。

4.折叠法

将关键字从左到右分成位数相同的几部分(最后一部分位数可以短一些),然后将这几部分叠加求和,根据哈希表地址个数取最后几位作为哈希地址。

适用于不知道关键字分布情况,而位数较多的情况。

5.随机数法

选择一个随机函数,关键字通过随机函数得到的函数值作为哈希地址。

Hash(key)=random(key)(random为随机函数)。

适用于关键字长度不等时的情况。

6.数学分析法

设有n个d位数,每位上有r种不同的符号,每种符号出现的频率不一定相同,可能在某几位上分布均匀,每种符号出现的机会均等,在某几位上分布不均匀,某几位符号反复出现,这时可根据哈希表的长度来选取分布均匀的若干位作为哈希地址。

例如以手机号作为关键字,手机号的前几位可能相同(即“在某几位上分布不均匀,某几位符号反复出现”),后几位不同(即“可能在某几位上分布均匀,每种符号出现的机会均等”)。这时就可以手机号后几位作为哈希地址。

适用关键字位数比较大,知道关键字分布且若干位分布均匀的情况。

2.4 哈希冲突的解决

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

2.4.1 闭散列

闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未填满,就说明哈希表中还有空余位置,就可以把发生哈希冲突的key存到“下一个”空位置。如何寻找下一个位置呢?

这里提出荷载因子的概念:有效数据个数/哈希表长度,荷载因子越大,代表哈希表中的元素越多,发生哈希冲突的概率就越大,开放定址法的荷载因子应在0.7~0.8左右。超过了就要扩容。

1.线性探测

从发生冲突的位置开始,依次往后开始寻找空位置。

代码实现:

// 哈希函数采用除留余数法
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 e : key){hash *= 31;hash += e;}return hash;}
};
//状态表示增加了DELETE,因为在查找发生哈希冲突的元素时,我们会从冲突位置开始往后找,直到找到或者遇到空位置,此时如果我们删除了处于两个发生哈希冲突的元素之间的元素时,如果我们直接将状态改为空,那么就找不到第二个发生哈希冲突的元素了,因为中间有空位置。
enum State
{EXIST,EMPTY,DELETE};template<class K, class V>
struct HashData
{pair<K, V> _kv;State _state = EMPTY;
};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) != nullptr) return false;//扩容if (_n * 10 / _tables.size() == 7){HashTable<K, V> newtable;newtable._tables.resize(_tables.size() * 2);for (int i = 0; i < _tables.size();i++){if(_tables[i]._state==EXIST)newtable.Insert(_tables[i]._kv);}swap(newtable._tables, _tables);}Hash t;int n = _tables.size();int pos = t(kv.first) % n;while (_tables[pos]._state != EMPTY){pos++;pos %= n;}_tables[pos]._kv = kv;_tables[pos]._state = EXIST;_n++;return true;}HashData<K, V>* Find(const K& key){Hash t;int n = _tables.size();int pos = t(key) % n;while (_tables[pos]._state!=EMPTY){if (key == _tables[pos]._kv.first&&_tables[pos]._state==EXIST) return &_tables[pos];pos++;pos %= n;}return nullptr;}bool Erase(const K& key){HashData<K, V>* target = Find(key);if (target == nullptr) return false;target->_state = DELETE;_n--;return true;}private:vector<HashData<K, V>> _tables;size_t _n = 0;  // 表中存储数据个数
};

优点:实现简单

缺点:一旦方式哈希冲突,所有的冲突连在一起,就容易发生数据的“堆积”,前一个元素因为哈希冲突而占了后一个元素的位置,以此发生堆积。

2.二次探测

Hash(key)=(key+d)%n,d={1^2,-1^2,2^2,-2^2,3^2,-3^2,……}。

发生哈希冲突时,先往后隔2个位置找,如果还是冲突,就往前隔2个位置找,如果还是冲突,就往后隔2^2个位置找,依次类推(按照d中的元素进行查看)。

开放定址法的缺陷就是空间利用率低,这也是哈希的缺陷。

2.4.2 开散列

开散列又叫链地址法(拉链法),我们将发生哈希冲突的元素挂在同一个哈希地址上。

这里具有相同哈希地址元素的集合称作桶,每个桶中的元素通过单链表连接。

闭散列的荷载因子为1。

代码实现:

// 哈希函数采用除留余数法
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 e : key){hash *= 31;hash += e;}return hash;}
};template<class T>
struct HashNode
{T _data;HashNode<T>* _next;HashNode(const T& data):_data(data), _next(nullptr){}
};// K 为 T 中key的类型
// T 可能是键值对,也可能是K
// KeyOfT: 从T中提取key
// Hash将key转化为整形,因为哈希函数使用除留余数法
template<class K, class T,class KeyOfT,class Hash>
class HashTable
{typedef HashNode<T> Node;
public:HashTable(){_tables.resize(10, nullptr);}// 哈希桶的销毁~HashTable(){for (int i = 0; i < _tables.size(); i++){Node* tmp = _tables[i];while (tmp){Node* next = tmp->_next;delete tmp;tmp = next;}}}// 插入值为data的元素,如果data存在则不插入bool Insert(const T& data){KeyOfT kot;if(Find(kot(data))) return false;Hash t;int n = _tables.size();//扩容if (_n / n == 1){HashTable newtable;newtable._tables.resize(n * 2);for (int i = 0; i < n; i++){Node* tmp = _tables[i];while (tmp){Node* next = tmp->_next;tmp->_next = newtable._tables[t(kot(tmp->_data)) % (n * 2)];newtable._tables[t(kot(tmp->_data)) % (n * 2)] = tmp;tmp = next;}_tables[i] = nullptr;}swap(_tables, newtable._tables);}int pos = t(kot(data)) % n;Node* newnode = new Node(data);newnode->_next = _tables[pos];_tables[pos] = newnode;_n++;}// 在哈希桶中查找值为key的元素,存在返回true否则返回falsebool Find(const K& key){Hash t;KeyOfT kot;int n = _tables.size();Node* tmp = _tables[t(key) % n];while (tmp){if (key == kot(tmp->_data)) return true;tmp = tmp->_next;}return false;}// 哈希桶中删除key的元素,删除成功返回true,否则返回falsebool Erase(const K& key){Hash t;KeyOfT kot;int n = _tables.size();int pos = t(key) % n;Node* cur = _tables[pos];Node* pre = nullptr;while (cur){if (kot(cur->_data) == key) break;pre = cur;cur = cur->_next;}if (cur == nullptr) return false;if (pre == nullptr){_tables[pos] = cur->_next;}else{pre->_next = cur->_next;}delete cur;_n--;return true;}private:vector<Node*> _tables;  // 指针数组size_t _n = 0;			// 表中存储数据个数
};

开散列相比闭散列更节省空间。

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

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

相关文章

数学建模笔记——层次分析法

数学建模笔记——层次分析法 数学建模笔记——层次分析法1. 层次分析法的基本原理和步骤2. 层次分析法的建模过程2.1 问题的提出2.2 模型原理2.3 为该问题建立层次结构模型2.4 构造判断矩阵1. 判断矩阵的含义2. 为该问题构造判断矩阵 2.5 一致性检验1. 一致性检验方法2. 对上述…

相机内存卡格式化了照片怎么恢复?格式化恢复详解

摄影爱好者们都知道&#xff0c;相机内存卡是记录我们美好瞬间的重要媒介。然而&#xff0c;在使用过程中&#xff0c;有时我们会因操作不当或设备故障&#xff0c;不小心格式化了内存卡&#xff0c;从而导致珍贵的照片丢失。面对这种情况&#xff0c;我们该如何恢复这些被格式…

电脑pe是什么意思_电脑pe系统作用详细分析

有些小白很好奇&#xff0c;电脑pe是什么意思?所谓的电脑pe系统其实就是当我们的电脑出现问题而不能进入正常系统时候的一种“紧急备用”系统。如果需要重装操作系统的话&#xff0c;以往采用光盘使用的比较多&#xff0c;随着技术的进步&#xff0c;用u盘制作一个pe启动盘去安…

【自然语言处理】实验一:基于NLP工具的中文分词

目录 前言 1. 导入jieba分词器 2. 用精确模式进行中文分词 3. 用全模式进行中文分词 4. 用搜索引擎进行中文分词 5. 利用 lcut返回结果列表(list) 总结 &#x1f308;嗨&#xff01;我是Filotimo__&#x1f308;。很高兴与大家相识&#xff0c;希望我的博客能对你有所帮助。 &a…

避免在C#循环中使用await

在C#中&#xff0c;异步编程因其能够提升应用程序性能和响应能力而变得越来越流行。async和await关键字使得编写异步代码变得更加容易&#xff0c;但如果使用不当&#xff0c;它们也可能引入一些陷阱。一个常见的错误是在循环中使用await&#xff0c;这可能导致性能瓶颈和意外行…

直播相关01-录制麦克风声音,QT上 .pro 将 linux,mac和windows上配置为三种可以共享, 在.pro文件中 message 的作用

一 QT 上的 .pro 文件 将 linux&#xff0c;mac和windows上配置设置为可以共享 1. 先来看文件夹布局 2. 再来看 QT 中的 .pro文件 .pro 文件的写法 QT core guigreaterThan(QT_MAJOR_VERSION, 4): QT widgetsCONFIG c11# The following define makes your compiler …

Spring框架的核心模块有哪些

Spring框架的核心模块构成了其基础架构&#xff0c;并为开发者提供了丰富的功能。以下是一些主要的Spring核心模块&#xff1a; Spring Core&#xff1a; 这是Spring框架中最基础的模块&#xff0c;提供了依赖注入&#xff08;DI&#xff09;功能&#xff0c;这是Spring的基石。…

职场答案薄

公司做大的过程就是创始人把职责一层层分摊下去的过程&#xff0c;公司里的各级领导在招聘时的原始诉求都是一样的&#xff0c;就是招到可以帮自己分担一部分工作的人&#xff0c;然后自己好集中精力去做更重要的工作 如何去做运营 1.流程制度&#xff08;三个目的&#xff1a;…

MyBaits的初理解

一.Mybaits的简介 Mybaits就是对JDBC的简化&#xff0c;就是对持久化的实现。 二.基础 需要导的dependencies <dependencies><!-- mybatis依赖 --><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId>&l…

STM32 HAL freertos零基础(二)-通过STM32CubeMX配置Freertos后在程序中进行任务创建,便于任务管理与识别。

1、简介 通过STM32CubeMX配置Freertos后&#xff0c;建立的任务都在freertos.c文件中&#xff0c;不易于观察&#xff0c;并且每次生成新任务还需要打开STM32CubeMX&#xff0c;本次教程讲解一种通过STM32CubeMX配置Freertos后在程序中进行任务创建&#xff0c;起到类似添加传…

【android10】【binder】【2.servicemanager启动——全源码分析】

系列文章目录 可跳转到下面链接查看下表所有内容https://blog.csdn.net/handsomethefirst/article/details/138226266?spm1001.2014.3001.5501文章浏览阅读2次。系列文章大全https://blog.csdn.net/handsomethefirst/article/details/138226266?spm1001.2014.3001.5501 目录 …

C语言 | Leetcode C语言题解之第394题字符串解码

题目&#xff1a; 题解&#xff1a; #define N 2000typedef struct {int data[30];;int top; } Stack;void push(Stack *s, int e) { s->data[(s->top)] e; }int pop(Stack *s) { return s->data[--(s->top)]; }//多位数字串转换成int int strToInt(char *s) {cha…

MySQL表操作

目录 查看表 ​查看指定表的结构 ​删除表 小试牛刀 MySQL表的增删改查&#xff08;CRUD&#xff09; 插入操作 新增 指定列插入 多行插入 查询表中数据 全列查询 指定列查询 ​编辑查询字段为表达式 ​编辑别名 时间日期的处理 插入一个时间 获取当前时间 查…

批量创建文件夹和文件——excel VBA实现

当需要创建大量文件夹及文件时&#xff0c;可借助excel vba 实现&#xff0c;如下图&#xff1a; 批量创建文件名为1-10的文件夹&#xff0c;每个文件夹内有个与文件名相同的txt文件&#xff0c;txt文件内的数字也跟文件名相同。 附代码&#xff1a; Sub CreateFoldersAndFile…

30年期国债期货合约介绍

30年期国债期货合约 30年期国债期货合约主要条款解读 合约标的 30年期国债期货采用名义标准券设计&#xff0c;一篮子可交割国债均可用于交割。30年期国债期货合约标的是面值为100万元人民币、票面利率为3%的名义超长期国债。 可交割国债范围 30年期国债期货合约可交割国债…

【Power Compiler手册】9.时钟门控(6)

使用安全寄存器插入时钟门控 你可以使用同一个时钟门控来门控三模冗余(TMR)寄存器,对所有安全寄存器进行操作,而不需要触碰或修改投票逻辑。 Design Compiler NXT 工具会自动检测是否使用了安全寄存器,并相应地插入时钟门控。该工具始终确保同一安全组内的安全寄存器共享…

在连通无向图中寻找正反向各通过每条边一次的路径(中国邮递员问题)

在连通无向图中寻找正反向各通过每条边一次的路径(中国邮递员问题) 引言问题定义算法思路具体步骤第一步:找出所有奇度顶点第二步:将奇度顶点配对,并添加最短路径第三步:构造欧拉回路伪代码C语言实现引言 在图论中,中国邮递员问题(Chinese Postman Problem, CPP)是一…

VuePress搭建个人博客(手动安装)

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

【信创】推荐一款在龙芯CPU终端上使用的WiFi接收器 _ 统信 _ 麒麟

原文链接&#xff1a;【信创】推荐一款在龙芯CPU终端上使用的WiFi接收器 | 统信 | 麒麟 Hello&#xff0c;大家好啊&#xff01;今天给大家带来一篇关于在龙芯CPU架构的台式机上如何安装和使用无线WiFi接收器的文章。对于使用龙芯CPU的台式机用户来说&#xff0c;安装并配置WiF…

Word文档的读取(1)

读取一个班的答题卡 解决方法&#xff1a; 导入os模块后&#xff0c;将乔老师的文件夹路径 /Users/qiao/answerKey 赋值给变量allKeyPath。使用os.listdir()函数获取该路径下所有的答题卡名称列表&#xff0c;并赋值给变量allItems。最后使用for循环遍历所有答题卡&#xff0c…