【C++】哈希(闭散列,开散列)
哈希
- unordered系列关联式容器
- unordered_map&& unordered_set
- 关联性容器介绍
- 介绍
- 底层结构
- 哈希概念
- 哈希冲突
- 闭散列
- 闭散列介绍
- 实现
- 开散列
- 介绍
- 开散列增容机制
- 实现
- 开散列和闭散列比较
unordered系列关联式容器
unordered_map&& unordered_set
关联性容器介绍
在STL底层map和set使用红黑树封装实现的,而其复杂度为基本为logn因为高度可控,但往后发展还是有大神想出了另一种容器结构也就是哈希。也就是底层用哈希来封装出map和set
介绍
- unordered_map是存储<key, value>键值对的关联式容器,其允许通过keys快速的索引到与其对应的value。
- 在unordered_map中,键值通常用于惟一地标识元素,而映射值是一个对象,其内容与此键关联。键和映射值的类型可能不同。
- 在内部,unordered_map没有对<kye, value>按照任何特定的顺序排序, 为了能在常数范围内找到key所对应的value,unordered_map将相同哈希值的键值对放在相同的桶中。
- unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低。
- unordered_maps实现了直接访问操作符(operator[]),它允许使用key作为参数直接访问value。
- 它的迭代器至少是前向迭代器。
注意:unordered_map中key是不能重复的,因此count函数的返回值最大为1
无论是unordered_map/se其使用规则和map和set没有多大差别就是底层实现不同。效率不同。
底层结构
哈希概念
顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即O(N),搜索的效率取决于搜索过程中元素的比较次数。
理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。 如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。
插入元素:
根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放
搜索元素:
对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功
该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(Hash Table)(或者称散列表)
例如:
每次给一个数对其进行取模,得到一个值对其进行插入,有点之前计数排序那意思。
差的元素多了不可避免地就会有重复的字眼。
哈希冲突
对于不同关键字取模相同出一样的哈希地址,这个我们叫做哈希冲突。
通过哈希函数来解决。
设计原则:
- 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1之间
- 哈希函数计算出来的地址能均匀分布在整个空间中
- 哈希函数应该比较简单
常见哈希函数:直接定制法,除留余数法,平方取中法,折叠法,随机数法,数学分析法。
经常用的就是前两个
注意:哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突
闭散列
闭散列介绍
闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个”空位置中去
1.线性探测
对于已经冲突的关键字,我们对其进行线性查找,通过哈希函数找到对应的位置,若没有就插入,若有就一次往后找空位置插入。
这里就对于散列的每一个位置进行状态的定义,采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索。比如删除元素4,如果直接删除掉,44查找起来可能会受影响。因此线性探测采用标记的伪删除法来删除一个元素
扩容机制:
当所有元素插满时候,其状态都为存在那么就无法找出对应空的位置一致循环下去,所以我们要保证闭散列不能为满或者不能使它快满了,在达到一定的比例时,就要对其进行扩容机制。
2.二次探测
可以通过一次探测得出如果同一块区域有大量的数据堆积在一起,效率会降低,也就是将一个个探测变成次方的查找
研究表明:当表的长度为质数且表装载因子a不超过0.5时,新的表项一定能够插入,而且任何一个位置都不会被探查两次。因此只要表中有一半的空位置,就不会存在表满的问题。在搜索时可以不考虑表装满的情况,但在插入时必须确保表的装载因子a不超过0.5,如果超出必须考虑增容。
空间利用率比较低,哈希的缺陷
实现
enum State{EXIST,EMPTY,DELETE,};template<class T>struct HashNode{State _state = EMPTY; // 状态T _t;};template<class K, class T, class HashFunc = Hash<K>>class HashTable{public:bool Insert(const T& t){// 负载因子>0.7就增容if (_tables.size() == 0 || _size * 10 / _tables.size() == 7){size_t newsize = GetNextPrime(_tables.size());HashTable<K, T> newht;newht._tables.resize(newsize);for (auto& e : _tables){if (e._state == EXIST){// 复用冲突时探测的逻辑newht.Insert(e._t);}}_tables.swap(newht._tables);}HashNode<T>* ret = Find(t);if (ret)return false;size_t start = t % _tables.size();// 线性探测,找一个空位置size_t index = start;size_t i = 1;while (_tables[index]._state == EXIST){index = start + i;index %= _tables.size();++i;}_tables[index]._t = t;_tables[index]._state = EXIST;_size++;return true;}
如果传入的是个字符串,那么我们就需要将其转成ASC||码进行计算,就需要引入仿函数特化一个结构体如果传入的是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 hash = 0;for (auto ch : s){//hash += ch;hash = hash * 131 + ch;}return hash;}
};
当然查找和删除也类似:查找存在并且相等的值,遇到空则失败。删除就得先查找然后设置状态。插入的时候要先对其进行查找如果有了就失败
开散列
介绍
开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。
开散列增容机制
开散列的扩容机制不会让自己的插入影响,如果通过一个桶中插入的节点过多不回去影响其他的只会影响自己的效率,最好的情况就是每个桶中一个节点就是不需要二次搜索,不会出现哈希冲突。
实现
当进入size为0就给第一个素数(这里大神们研究的通过开成某些素数大小的容量不容易冲突),创建一个新对象并将旧表上的点一个个的插入新表当中,然后释放旧表节点并至空。
pair<Node*, bool> Insert(const T& t){KeyOfT kot;// 负载因子 == 1时增容if (_size == _tables.size()){size_t newsize = GetNextPrime(_tables.size());vector<Node*> newtables;newtables.resize(newsize, nullptr);for (size_t i = 0; i < _tables.size(); i++){// 旧表中节点直接取下来挂到新表Node* cur = _tables[i];while (cur){Node* next = cur->_next;size_t index = HashFunc(kot(cur->_t), newtables.size());// 头插cur->_next = newtables[index];newtables[index] = cur;cur = next;}_tables[i] = nullptr;}newtables.swap(_tables);}size_t index = HashFunc(kot(t), _tables.size());// 查找t在在不在Node* cur = _tables[index];while (cur){if (kot(cur->_t) == kot(t))return make_pair(cur, false);cur = cur->_next;}Node* newnode = new Node(t);newnode->_next = _tables[index];_tables[index] = newnode;return make_pair(newnode, true);}
开散列和闭散列比较
应用链地址法处理溢出,需要增设链接指针,似乎增加了存储开销。事实上: 由于开地址法必须保持大量的空闲空间以确保搜索效率,如二次探查法要求装载因子a <= 0.7,而表项所占空间又比指针大的多,所以使用链地址法反而比开地址法节省存储空间
【C++】哈希(闭散列,开散列)相关推荐
- 【C++】哈希——unordered系列容器|哈希冲突|闭散列|开散列
文章目录 一.unordered系列关联式容器 二.哈希概念 三.哈希冲突 四.哈希函数 五.解决哈希冲突 1.闭散列--开放定址法 2.代码实现 3.开散列--开链法 4.代码实现 六.结语 一.u ...
- 哈希(Hash) - 开散列/闭散列
文章目录: 认识哈希 哈希函数 处理冲突的方法 闭散列(开放定址法) 开散列(链地址法) 哈希表闭散列实现 闭散列基本框架 哈希表闭散列插入(insert) 哈希表闭散列删除(erase) 哈希表闭散 ...
- 【c++】哈希---unordered容器+闭散列+开散列
1.unordered系列关联式容器 在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时效率可达到 logN,即最差情况下需要比较红黑树的高度次,当树中的节点非常多时,查询效率也 ...
- C++---哈希闭散列与开散列
产生原因 在顺序结构或树形结构的数据集合中,我们想要查询一个元素时,必须进行遍历,所以顺序结构的查询时间复杂度为O(N),树形结构查询的时间复杂度为O(log2^N),但是我们想要一种不用遍历就知道其 ...
- 哈希之开散列,闭散列
先从数据查找开始说起吧,在线性结构,树形结构当中查找一个元素必须经过多次和一些元素进行比较,然后通过比较,查找到对应元素,这种方法多多少少,时间复杂度都是比较高的. 有没有一种方法时间复杂度,仅仅O( ...
- C++:哈希(闭散列、开散列)
文章目录 哈希概念 哈希冲突 哈希函数 哈希冲突解决 闭散列 什么时机增容,如何增容? 线性探测的实现 开散列 开散列增容 开散列的实现 开散列与闭散列比较 unordered_map模拟实现(应用开 ...
- 【C++哈希桶(开散列)】
1. 开散列(哈希桶)概念 开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各 ...
- 哈希 ---《哈希函数》------除数的选取为什么是质数?、《哈希冲突》------解决方法、《闭散列》、《开散列》
一.哈希概念 顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较**.顺序查找时间复杂度为O(N),平衡树中为树的高度,即O(logN ) ...
- 哈希---闭散列和开散列
闭散列法和开散列法解决哈希冲突 闭散列: 也叫开放定址法,当发生哈希冲突时,寻找合适的空位置 找空位置的方法: 线性探测法 从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止 线性探测优点 ...
最新文章
- 什么是URL?协议头,路径和端口是什么意思?
- 关上Deepfake的潘多拉魔盒,RealAI推出深度伪造视频检测工具
- Flutter 制作漂亮的登录表单
- 【求助】小系统组成大系统所遇到的问题
- VC++学习(15):多线程
- 算法---宝藏最优选择问题
- java 删除二维数组中的null_避免在Java中检查Null语句
- Linux的capability深入分析(1)
- 的mvc_浅谈MVC
- Http Header的Transfer-Encoding
- DirectX11 With Windows SDK--03 索引缓冲区、常量缓冲区
- markdown语法
- mac 无法对计算机进行更改,解析深度技术win10 MAC不能修改的原因和解决成功的技巧...
- 基于SpringBoot微信公众号的开发
- 51单片机数码管计数器
- 阿里云建站:企业网站定制/速成美站/响应式功能建站官方购买及优惠详解!
- Auto.js实现自动删除朋友圈照片
- lopa分析_什么是LOPA分析?
- 佛山机场安装RFID行李跟踪系统 可全流程追踪行李
- python扇贝单词书_扇贝单词如何创建自己的单词书
热门文章
- 什么是RAID? RAID详解
- C#与sqlserver开发问题
- 【笔记】Cocos2d-x高级开发教程:制作自己的捕鱼达人 笔记一:序_前言_第一章
- 桌面上的软件图标是白板_是时候结束对软件工程师的白板采访了
- Django+Scrapy完成微博首页热点的提取和网页显示
- 庄小威超分辨storm_庄小威15岁进中科大少年班,中国天才却成美国院士,如今又获大奖...
- 大数定律及中心极限定理6
- webpack3.6.0安装request to htttp://registry.npmjs.org/webpack failed,reason:connect ETIMEDOUT 104.16.2
- 赴一场时光之约 遇一场手作邂逅
- JavaScript学习笔记——firstchild的问题