C++语法 | 相关知识点 | 可以通过点击 | 以下链接进行学习 | 一起加油! |
---|---|---|---|---|
命名空间 | 缺省参数与函数重载 | C++相关特性 | 类和对象-上篇 | 类和对象-中篇 |
类和对象-下篇 | 日期类 | C/C++内存管理 | 模板初阶 | String使用 |
String模拟实现 | Vector使用及其模拟实现 | List使用及其模拟实现 | 容器适配器Stack与Queue | Priority Queue与仿函数 |
模板进阶-模板特化 | 面向对象三大特性-继承机制 | 面向对象三大特性-多态机制 | STL 树形结构容器 | 二叉搜索树 |
AVL树 | 红黑树 | 红黑树封装map/set |
本篇将简要介绍哈希表的基础知识,为后续文章深入探讨奠定基础
?个人主页:是店小二呀
?C语言专栏:C语言
?C++专栏: C++
?初阶数据结构专栏: 初阶数据结构
?高阶数据结构专栏: 高阶数据结构
?Linux专栏: Linux
?喜欢的诗句:无人扶我青云志 我自踏雪至山巅
文章目录
一、unordered系列关联式容器 1.1 unordered_map 1.2 unordered_set1.3 unordered_set/map与set/map探讨1.3.1 unordered_set和unordered_map探讨1.3.2 set和map探讨1.3.4 容器使用推荐 二、哈希概念三、哈希冲突四、哈希函数 4.1 哈希函数设计原则4.2 常见哈希函数 五、 哈希冲突解决
一、unordered系列关联式容器
在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时效率可达到logN,即最差情况下需要比较红黑树的高度次,当树中的节点非常多时,查询效率也不理想。最好的查询是,进行很少的比较次数就能够将元素找到,因此在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同,本文中只对unordered_map和unordered_set进行介绍。
1.1 unordered_map
unordered_map文档介绍
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。它的迭代器至少是前向迭代器。1.2 unordered_set
unordered_set文档介绍
无序集合是以特定顺序存储唯一元素的容器,它允许根据单个元素的值快速检索它们。在unordered_set
中,元素的值同时是其键,用于唯一标识它。键是不可变的,因此,unordered_set
中的元素在容器中一次就不能修改 - 但是可以插入和删除它们。在内部,unordered_set
中的元素不按任何特定顺序排序,而是根据其哈希值组织到存储桶中,以便直接按其值快速访问单个元素(平均平均时间复杂度恒定)。unordered_set
容器通过其键访问单个元素的速度比set容器更快,尽管它们通常通过其元素的子集进行范围迭代的效率较低。容器中的迭代器至少是前向迭代器。 【场景练习】
1.3 unordered_set/map与set/map探讨
这里unordered_set 和 unordered_map 跟set和map相似度高达90%,主要有三点不同:数据是否有序,性能如何,底层角度区分适合场景。还有unordered_set 和 unordered_map只有单向迭代器。
1.3.1 unordered_set和unordered_map探讨
特点:
基于哈希表实现。访问时间复杂度平均为 O(1),最坏情况下为 O(n)(当哈希冲突严重时)。不保持元素的顺序。使用场景:
当你只关心元素的存在与否,而不需要关心其顺序时,可以选择这两种容器。对于频繁插入、删除操作的场景,unordered_set
和 unordered_map
性能优于有序容器。如果主要需求是查找速度,且不需要数据有序,unordered_set
和 unordered_map
更为合适。 1.3.2 set和map探讨
特点:
基于红黑树实现。访问时间复杂度为 O(log n)。元素按键的顺序存储。使用场景:
当你需要按顺序遍历元素,或需要保持元素的顺序时,使用set
和 map
是合适的选择。如果需要频繁进行范围查询或按顺序访问元素,这两种容器更有优势,尽管它们在查找上的性能稍慢. 1.3.4 容器使用推荐
哈希表(unordered_map/unordered_set):如果只关注查找和操作效率,且不需要顺序,哈希表的性能更强,适合大多数查找操作(平均 O(1))。
红黑树(map/set):如果需要数据有序,稳定的性能,或者需要范围查询、按顺序处理数据,红黑树更强。
二、哈希概念
顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即O(logN),搜索的效率取决于搜索过程中元素的比较次数。
理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。
如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素
当向该结构中:
插入元素:根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放搜索元素:对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(Hash Table)(或者称散列表
哈希表通过哈希函数直接计算出数据存储的位置,因此在不发生哈希冲突的情况下,能够在常数时间内完成查找。用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快
三、哈希冲突
对于两个数据元素的关键字ki和 kj(i != j),有ki != kj,但有:Hash(ki) ==Hash( kj),即:不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞 ,把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”
四、哈希函数
引起哈希冲突的一个原因可能是:哈希函数设计不够合理。
4.1 哈希函数设计原则
哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1之间哈希函数计算出来的地址能均匀分布在整个空间中哈希函数应该比较简单
4.2 常见哈希函数
直接定址法–(常用)
取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B
优点:简单、均匀
缺点:需要事先知道关键字的分布情况
使用场景:适合查找比较小且连续的情况
除留余数法–(常用)
设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,
按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址
平方取中法–(了解)
假设关键字为1234,对它平方就是1522756,抽取中间的3位227作为哈希地址;
再比如关键字为4321,对它平方就是18671041,抽取中间的3位671(或710)作为哈希地址
平方取中法比较适合:不知道关键字的分布,而位数又不是很大的情况
折叠法–(了解)
折叠法是将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这
几部分叠加求和,并按散列表表长,取后几位作为散列地址。
折叠法适合事先不需要知道关键字的分布,适合关键字位数比较多的情况
随机数法–(了解)
选择一个随机函数,取关键字的随机函数值为它的哈希地址,即H(key) = random(key),其中
random为随机数函数。
通常应用于关键字长度不等时采用此法
数学分析法–(了解)
设有n个d位数,每一位可能有r种不同的符号,这r种不同的符号在各位上出现的频率不一定
相同,可能在某些位上分布比较均匀,每种符号出现的机会均等,在某些位上分布不均匀只
有某几种符号经常出现。可根据散列表的大小,选择其中各种符号分布均匀的若干位作为散
列地址。例如
假设要存储某家公司员工登记表,如果用手机号作为关键字,那么极有可能前7位都是 相同
的,那么我们可以选择后面的四位作为散列地址,如果这样的抽取工作还容易出现 冲突,还
可以对抽取出来的数字进行反转(如1234改成4321)、右环位移(如1234改成4123)、左环移
位、前两数与后两数叠加(如1234改成12+34=46)等方法。
数字分析法通常适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的
若干位分布较均匀的情况
哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突
五、 哈希冲突解决
解决哈希冲突两种常见的方法是:闭散列和开散列
闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去开散列 :开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中在后续两章将专门闭散列和开散列介绍,如何解决哈希冲突及其是实现哈希表结构。
以上就是本篇文章的所有内容,在此感谢大家的观看!这里是店小二呀C++笔记,希望对你在学习C++语言旅途中有所帮助!