山东官网建设公司/网站关键词优化报价
unordered系列关联式容器
在之前的博文中介绍过关联式容器中的map与set,同map与set一样,unordered_set与unordered_set也是关联式容器。
在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,查询效率可以达到logN;在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器的使用方法类似,但是其底层的实现不同。
unordered_map与unordered_set的底层实现都是哈希表。unordered关联容器与其他关联容器的区别是:
- unordered是单项迭代器
- unordered遍历出来的数据不是有序的
- unordered_set只能去重。不可以用于排序
- unordered_map也不可以用于排序
- 无序数据可以使用unordered关联容器具有优势,有序数据使用其他容器较快。
unordered_map的介绍
unordered_map的文档介绍
std::unordered_map
- unordered_map是存储< key,value >键值对的关联式容器,其允许通过keys快速的索引到与其对应的value。
- 在unordered_map中,键值通常用于唯一地标识元素,而映射值是一个对象,其内容与此键关联。键和映射值的类型可能不同。
- 在内部,unordered_map没有对< key,value >按照任何特定的顺序排序,为了可以在常数范围内找到key所对应的value,unordered_map将相同哈希值的键值对放在相同的桶中。
- unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代器方面效率较低。
- unordered_map实现了直接访问操作符operator[ ],它允许使用key作为参数直接访问value。
- unordered_map的迭代器至少是前向迭代器。
unordered_map的接口说明
unordered_map的构造
函数说明 | 功能介绍 |
---|---|
unordered_map | 构造不同格式的unordered_map的对象 |
unordered_map的容量
函数说明 | 功能介绍 |
---|---|
bool empty() const noexcept; | 检测unordered_map是否为空 |
size_type size() const noexcept; | 获取unordered_map的有效元素个数 |
unordered_map的迭代器
函数说明 | 功能介绍 |
---|---|
begin | 返回unordered_map第一个元素的迭代器 |
end | 返回unordered_map最后一个元素下一个位置的迭代器 |
rbegin | 返回unordered_map第一个元素的const迭代器 |
rend | 返回unordered_map最后一个元素下一个位置的const迭代器 |
unordered_map的元素访问
函数说明 | 功能介绍 |
---|---|
operator[] | 返回与key对应的value,没有一个默认值 |
【注意】:该函数实际上调用了哈希桶的插入操作,用参数key与value构造一个默认值往底层哈希桶中插入,如果key不在哈希桶中,插入成功,返回value;插入失败,说明key已经在哈希桶值,将key对应的value返回。
unordered_map的查询
函数说明 | 功能介绍 |
---|---|
iterator find ( const key_type& k ); | 返回key在哈希桶中的位置 |
size_type count ( const key_type& k ) const; | 返回哈希桶中关键值为key的键值对的个数 |
【注意】:unordered_map中的key是不能重复的,因此count函数的返回值最大为1
unordered_map的修改操作
函数说明 | 功能介绍 |
---|---|
insert | 向容器中插入键值对 |
erase | 删除容器中的键值对 |
clear | 清空容器中有效元素的个数 |
swap | 交换俩个容器中的元素 |
unordered_map桶操作
函数说明 | 功能介绍 |
---|---|
bucket_count | 返回哈希桶中的桶的总数 |
bucket_size | 返回n号桶中有效元素的个数 |
bucket | 返回元素key所在的桶号 |
哈希底层结构
哈希概念
哈希(散列):存储的值跟存储位置建立出一个对应关系。
顺序容器以及平衡树中,元素关键码与其存储位置之间没有对应关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即O(logN),搜索的效率取决于搜索过程中的元素的比较次数。
下面介绍一种方法:哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(Hash Table)或者散列表。
该方法可以不经过任何比较,一次直接从表中得到想要的元素,通过某种函数使元素的存储位置与其关键码之间能够建立一一映射的关系,在查找的时候通过该函数可以很快找到该元素。
插入元素:根据待插入的关键码,以此函数计算出该元素的存储位置并按照此位置进行存放。
搜索元素:对元素的关键码进行同样的计算,把求得的函数值当作元素的存储位置,在结构中按照此位置取元素比较,若关键码相等,则搜索成功。
哈希设计介绍
哈希函数设计原则:
- 哈希函数的定义域必须包括存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1之间
- 哈希函数计算出来的地址能均匀分布在整个空间中。
常见哈希函数:
1.直接定址法
取关键字的某个线性函数为散列地址:Hash(key) = A*key + B;
【优点】:简单,均匀
【缺点】:需要事先知道关键字的分布情况
【使用场景】:适合查找范围小,数据集中的情况。
2.除留余数法
设散列表中允许的地址数为m,取一个不大于m,但是最接近或者等于m的质数p作为除数,按照哈希函数:Hash(key)=key%p(p<=m),将关键码转换成哈希地址。

哈希冲突
哈希冲突: 不同关键字通过相同哈希函数计算出相同的哈希地址,这种现象是哈希冲突,也称为哈希碰撞。
解决哈希冲突的俩种常见的方法是:闭散列和开散列
闭散列
闭散列:也叫开放地址法,当发生哈希冲突的时候,如果哈希表未被填满,说明了在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个”空位置中去。
1.线性探测
线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置。
插入方式:
- 通过哈希函数获取待插入元素在哈希表中的位置
- 如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,使用线性探测找到下一个空位置,插入新元素。
删除方式:
- 采用闭散列处理哈希冲突的时候,不能随便物理删除哈希表中已经存在的元素,这样可能会导致影响其他元素的搜索。
- 可以采用标记的伪删除的方式来删除一个元素
enum STATE{EXIST,EMPTY,DELETE};
哈希表的扩容
如果使用开放地址法,可以利用载荷因子:
散列表的载荷因子:a = 填入表中的元素个数 / 散列表的长度
a是散列表装满程度的标志因子,由于散列表的长度是定值,a与“填入表中的元素个数”成正比。
- 负载因子越大,冲突概率越大,空间利用率更高
- 负载因子越小,冲突概率越小,空间利用率更低(空间浪费越多)
哈希表不能满了再扩容,控制负载因子再0.7-0.8之间。如果超过0.8,查表时的CPU缓存不命中,按照指数曲线上升。
在扩容之后映射关系需要改变,重新映射,这样也会导致哈希冲突的变化。
线性探测的实现
#include<iostream>
#include<string>using namespace std;template<class K>
struct DefaultHashFunc
{size_t operator()(const K& key){return (size_t)key;}
};template<>
struct DefaultHashFunc<string>
{size_t operator()(const string& str){// BKDRsize_t hash = 0;for (auto ch : str){hash *= 131;hash += ch;}return hash;}
};namespace open_address
{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 HashFunc = DefaultHashFunc<K>>class HashTable{public:HashTable(){_table.resize(10);}bool Insert(const pair<K, V>& kv){if (Find(kv.first)){return false;}// 扩容//if ((double)_n / (double)_table.size() >= 0.7)if (_n * 10 / _table.size() >= 7){size_t newSize = _table.size() * 2;// 遍历旧表,重新映射到新表HashTable<K, V, HashFunc> newHT;newHT._table.resize(newSize);// 遍历旧表的数据插入到新表即可for (size_t i = 0; i < _table.size(); i++){if (_table[i]._state == EXIST){newHT.Insert(_table[i]._kv);}}_table.swap(newHT._table);}// 线性探测HashFunc hf;size_t hashi = hf(kv.first) % _table.size();while (_table[hashi]._state == EXIST){++hashi;hashi %= _table.size();}_table[hashi]._kv = kv;_table[hashi]._state = EXIST;++_n;return true;}HashData<const K, V>* Find(const K& key){// 线性探测HashFunc hf;size_t hashi = hf(key) % _table.size();while (_table[hashi]._state != EMPTY){if (_table[hashi]._state == EXIST&& _table[hashi]._kv.first == key){return (HashData<const K, V>*) & _table[hashi];}++hashi;hashi %= _table.size();}return nullptr;}// 按需编译bool Erase(const K& key){HashData<const K, V>* ret = Find(key);if (ret){ret->_state = DELETE;--_n;return true;}return false;}private:vector<HashData<K, V>> _table;size_t _n = 0; // 存储有效数据的个数};
}
【线性探测的优点】:实现简单
【线性探测的缺点】:一旦发生哈希冲突,所有的冲突连在一起,容易产生数据“堆积”,即不同关键码占据了棵利用的空位置,使得寻找某关键码的位置需要多次比较,导致搜索效率低下。
2.二次探测
线性探测的缺点是产生冲突之后,会将数据堆积在一起,这与其找下一个空位置有关系,查找数据需要逐个进行查找,可以使用二次探测解决问题。
二次探测:
找下一个空位置的方法:
hashi = key % n
hashi + i ^2(i为移动的位置)
i>=0
【研究表明】:当表的长度为质数且表装载因子a不超过0.5时,新的表项一定能够插入,而且任 何一个位置都不会被探查两次。因此只要表中有一半的空位置,就不会存在表满的问题。在搜索时可以不考虑表装满的情况,但在插入时必须确保表的装载因子a不超过0.5,如果超出必须考虑增容。
闭散列最大的缺陷就是空间利用率低,这也是哈希的缺陷。
开散列
开散列:开散列法又称为链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一个子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。
【说明】开散列使用的是哈希桶,如果不扩容,就会不断插入,某些桶就会越来越长,效率得不到保障,负载因子适当放大,一般负载因子控制到1,平均下来,每一个桶一个数据。
开散列的实现
// 泛型编程:不是针对某种具体类型,针对广泛的类型(两种及以上) -- 模板
namespace hash_bucket
{template<class T>struct HashNode{T _data;HashNode<T>* _next;HashNode(const T& data):_data(data), _next(nullptr){}};// 前置声明template<class K, class T, class KeyOfT, class HashFunc>class HashTable;template<class K, class T, class KeyOfT, class HashFunc>struct HTIterator{typedef HashNode<T> Node;typedef HTIterator<K, T, KeyOfT, HashFunc> Self;Node* _node;HashTable<K, T, KeyOfT, HashFunc>* _pht;HTIterator(Node* node, HashTable<K, T, KeyOfT, HashFunc>* pht):_node(node), _pht(pht){}T& operator*(){return _node->_data;}T* operator->(){return &_node->_data;}Self& operator++(){if (_node->_next){// 当前桶还没完_node = _node->_next;}else{KeyOfT kot;HashFunc hf;size_t hashi = hf(kot(_node->_data)) % _pht->_table.size();// 从下一个位置查找查找下一个不为空的桶++hashi;while (hashi < _pht->_table.size()){if (_pht->_table[hashi]){_node = _pht->_table[hashi];return *this;}else{++hashi;}}_node = nullptr;}return *this;}bool operator!=(const Self& s){return _node != s._node;}};// set -> hash_bucket::HashTable<K, K> _ht;// map -> hash_bucket::HashTable<K, pair<K, V>> _ht;template<class K, class T, class KeyOfT, class HashFunc = DefaultHashFunc<K>>class HashTable{typedef HashNode<T> Node;// 友元声明template<class K, class T, class KeyOfT, class HashFunc>friend struct HTIterator;public:typedef HTIterator<K, T, KeyOfT, HashFunc> iterator;iterator begin(){// 找第一个桶for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];if (cur){return iterator(cur, this);}}return iterator(nullptr, this);}iterator end(){return iterator(nullptr, this);}HashTable(){_table.resize(10, nullptr);}~HashTable(){for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];while (cur){Node* next = cur->_next;delete cur;cur = next;}_table[i] = nullptr;}}bool Insert(const T& data){KeyOfT kot;if (Find(kot(data))){return false;}HashFunc hf;// 负载因子到1就扩容if (_n == _table.size()){size_t newSize = _table.size() * 2;vector<Node*> newTable;newTable.resize(newSize, nullptr);// 遍历旧表,顺手牵羊,把节点牵下来挂到新表for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];while (cur){Node* next = cur->_next;// 头插到新表size_t hashi = hf(kot(cur->_data)) % newSize;cur->_next = newTable[hashi];newTable[hashi] = cur;cur = next;}_table[i] = nullptr;}_table.swap(newTable);}size_t hashi = hf(kot(data)) % _table.size();// 头插Node* newnode = new Node(data);newnode->_next = _table[hashi];_table[hashi] = newnode;++_n;return true;}Node* Find(const K& key){HashFunc hf;KeyOfT kot;size_t hashi = hf(key) % _table.size();Node* cur = _table[hashi];while (cur){if (kot(cur->_data) == key){return cur;}cur = cur->_next;}return nullptr;}bool Erase(const K& key){HashFunc hf;KeyOfT kot;size_t hashi = hf(key) % _table.size();Node* prev = nullptr;Node* cur = _table[hashi];while (cur){if (kot(cur->_data) == key){if (prev == nullptr){_table[hashi] = cur->_next;}else{prev->_next = cur->_next;}--_n;delete cur;return true;}prev = cur;cur = cur->_next;}return false;}void Print(){for (size_t i = 0; i < _table.size(); i++){printf("[%d]->", i);Node* cur = _table[i];while (cur){cout << cur->_kv.first << ":" << cur->_kv.second << "->";cur = cur->_next;}printf("NULL\n");}cout << endl;}private:vector<Node*> _table; // 指针数组size_t _n = 0; // 存储了多少个有效数据};
}
开散列与闭散列的比较
优先使用开散链的方法。
虽然链地址的方法处理溢出,需要增设链接指针,但是开地址法必须保持大量的空闲空间以确保搜索效率,而表项所占的空间比指针大,所以使用链地址法比开地址法节省空间。
相关文章:

【C++】哈希容器
unordered系列关联式容器 在之前的博文中介绍过关联式容器中的map与set,同map与set一样,unordered_set与unordered_set也是关联式容器。 在C98中,STL提供了底层为红黑树结构的一系列关联式容器,查询效率可以达到logN;在…...

milvus - VectorDBBench benchmaker 性能测试工具使用经验
IVF_FLAT (Inverted File with Flat Indexing) 优点: 在数据量适中且维度不是非常高的情况下,IVF_FLAT能提供精确的最近邻搜索结果。 相对简单,易于理解和实现。 缺点: 当数据集非常大时,IVF_FLAT需要大量的内存来存储整个数据集,…...

Linux上如何分析进程内存分配,优化进程内存占用大小
云计算场景下,服务器上内存宝贵,只有尽可能让服务器上服务进程占用更少的内存,方才可以提供更多的内存给虚拟机,卖给云客户。 虚拟化三大件:libvirt、qemu、kvm内存开销不小,可以优化占用更少的内存。如何找到进程内存开销的地方直观重要,以qemu为例说明。 一、查看进…...

C语言笔记(第n版):知识清单
注:本文参考自【C reference - cppreference.com】和【C 语言参考 | Microsoft Learn】,颇有点借花献佛的意味…… C 程序是一系列包含声明的文本文件(通常为头文件和源文件)的序列。它们经过转换成为可执行程序,当操作…...

【香橙派系列教程】(四)基于ARM-Linux架构的语音控制刷抖音项目
【四】基于ARM-Linux架构的语音控制刷抖音项目 文章目录 【四】基于ARM-Linux架构的语音控制刷抖音项目1.语音模块配置1.创建产品2.引脚配置3.词条定义4.添加控制5.发布版本6.烧录固件 2.编程实现语音和开发板通信3.手机接入Linux热拔插1.dmesg命令2.adb调试踩坑问题 3.总结 4.…...

Java----反射
什么是反射? 反射就是允许对成员变量、成员方法和构造方法的信息进行编程访问。换句话来讲,就是通过反射,我们可以在不需要创建其对象的情况下就可以获取其定义的各种属性值以及方法。常见的应用就是IDEA中的提示功能,当我…...

相似度计算方法
一、相似度计算方法 相似度算法是计算两个或多个对象之间相似程度的方法,这些对象可以是文本、图像、音频等不同类型的数据。在计算机科学、信息检索、推荐系统、数据挖掘等领域中,相似度算法具有广泛的应用。 二、应用场景 搜索引擎:用于文…...

Vue 点击markdown页内链接,路由设置不跳转
在路由index.js里添加路由守卫: router.beforeEach((to,from,next)>{//如果是md页内链接“#xxx”,则不跳转const hash window.location.hash;if(hash.startsWith(#)) {next(false);}else{...其他控制代码next();} });当markdown用[标题链接](#标题名…...

IOday4
一、思维导图 二、练习 1、使用父子进程完成两个文件的拷贝,父进程拷贝前一半内容,子进程拷贝后一半内容,子进程结束后退出,父进程回收子进程的资源 #include<myhead.h> int main(int argc, const char *argv[]) {//判断终…...

智能座舱背后主流车机平台(SA8155/SA8295)的高通Hexagon DSP是什么?
智能座舱背后主流车机平台(SA8155/SA8295)的高通Hexagon DSP是什么? 一、高通Hexagon DSP的辉煌发展历程 高通,作为全球领先的无线通信技术创新者,其处理器技术一直走在行业前列。随着智能手机和物联网设备的普及,对处理器性能的…...

linux进程控制——进程等待——wait、waitpid
前言:本节内容仍然是进程的控制,上一节博主讲解的是进程控制里面的进程创建、进程退出、终止。本节内容将讲到进程的等待——等待是为了能够将子进程的资源回收,是父进程等待子进程。 我们前面的章节也提到过等待, 那里的等待是进…...

Shell脚本的进程管理
进程管理是系统管理的重要方面,通过对进程的监控、启动、停止和重启,可以保证系统的稳定运行。Shell脚本是一种强大的工具,可以对进程进行自动化管理,提高效率和准确性。 参考:shell脚本进程管理 - CSDN文库 shell脚本…...

JLink烧录失败
1. 现象: 这个位置是灰色的,没有SW Device信息。 MDK下面的打印: J-Flash的打印: windows上面的弹框的现象没有截屏。 2. 解决办法: 1.打开J-Link Commander,输入unlock kinetis,看现象不起作用,网…...

Monorepo简介
Monorepo 第一章:与Monorepo的邂逅第二章:Multirepo的困境第三章:Monorepo的魔力 - 不可思议的解决问题能力第四章:Monorepo的挑战与应对策略第五章:总结第六章:参考 第一章:与Monorepo的邂逅 …...

SpringBoot打包为jar包,打包前注意事项及打包教程
在打包 Spring Boot 项目为 JAR 包之前,有一些重要的注意事项和步骤,以确保打包过程顺利并生成一个可正常运行的 JAR 包: 1. 检查依赖和版本 确保所有依赖项和插件版本是最新且兼容的,特别是 Spring Boot 版本和其相关依赖的版本…...

B端系统UI个性化设计:感受定制之美
B端系统UI个性化设计:感受定制之美 引言 艾斯视觉作为ui设计和前端开发从业者,其观点始终认为:在当今竞争激烈的商业环境中,B端(Business-to-Business)系统的设计不再仅仅是功能性的堆砌,而是…...

前端常用 utils 工具封装
// 函数防抖 export function debounce(fn, interval) {let timerreturn function (this, ...args) {clearTimeout(timer)const context thislet params [...args]timer setTimeout(() > {fn.call(context, ...params)}, interval || 1000)} }// 函数节流 export functio…...

项目都做完了,领导要求国际化????--JAVA后端篇
springboot项目国际化相信各位小伙伴都会,很简单,但是怎么项目都做完了,领导却要求国际化文件就很头疼了 国际化的SpringBoot代码: 第一步:创建工具类 /*** 获取i18n资源文件** author bims*/ public class Message…...

国内备受好评PostgreSQL数据库性能如何?
为什么国内很多数据库采用PostgreSQL数据库作为基础,再次开发自己的产品呢?不仅仅是因为PostgreSQL数据库开源免费、PostgreSQL 数据库的性能也是相当出色的,具有以下几个方面的特点: 1. 处理大规模数据: - 能够有效地管理和处…...

彻底搞懂前端跨域解决方案
一、浏览器的同源策略 1、同源策略概述 同源策略是浏览器为确保资料安全,而遵循的一种策略,该策略对访问资源进行了一些限制。 2、什么是源(origin)? 3、示例 4、同源请求 5、非同源请求 二、跨域会受到哪些限制 1…...

Kafka基础概念
MQ消息中间件 1)总览: 消息中间件 这里我们主要学习的是kafka的基础概念 具体参考黑马头条:https://www.bilibili.com/video/BV1Qs4y1v7x4/?spm_id_from333.337.search-card.all.click 2)消息中间件对比 3)Kafka介…...

【论文阅读笔记】DeepCAD: A Deep Generative Network for Computer-Aided Design Models
1 引言 现有3D生成模型: 3D点云:大量离散的3D点组成的数据表示形式; 多边形网格:一系列相连的多边形组成的3D模型; 水平集场:使用数值函数来表示物体的边界,并根据函数值的正负来确定物体内部…...

《如鸢》开通官号,女性向游戏爆款预定
今天,备受瞩目的沉浸式剧情卡牌手游《如鸢》正式开通了官方社媒账号并发布了玩家信。 《如鸢》由灵犀互娱倾力打造,游戏不仅拥有跌宕起伏的权谋剧情,更采用Live2D技术,为玩家带来沉浸式的游戏体验,吸引了众多玩家关注。…...

OpenAI再下一城:发布Voice Engine,可使用文本和参考语音合成说话者的新语音!
转自 机器学习算法工程师 OpenAI又发布了一个最新的工作:Voice Engine。Voice Engine可以使用文本输入和单个 15 秒音频样本生成听起来自然且与原始说话者非常相似的语音。而且,一个小型模型仅通过一个 15 秒的样本就能创造出富有情感且逼真的语音。Voi…...

KVM高级功能部署
一、概述 KVM(Kernel-based Virtual Machine)是一种基于内核的虚拟化技术,它依赖于CPU的虚拟化扩展(如Intel VT和AMD-V)来实现虚拟机的创建、管理和调度。KVM虚拟化技术因其高效、稳定的特点,在云计算和企…...

【C语言】柔性数组(打开前所未见的大门)
文章目录 前言柔性数组1.1 概念1.2 柔性数组的特点1.3 柔性数组的使用1.4 柔性数组的优势 总结 前言 说到柔性数组,相信有很多学过C语言的读者都不知道这是个什么东西。不过没有关系,相信本章能够带你从到认识到掌握柔性数组,做一个充满知识…...

设计模式17-适配模式
设计模式17-适配模式 动机定义与结构C代码推导总结应用具体应用示例 动机 在软件系统中由于应用环境的变化常常需要将一些现存的对象。放到新的环境中去应用。但是新环境要求的接口是这些现存对象所不满足的。那么这种情况下如何应对这种迁移的变化?如何既能利用现…...

react ant Input defaultValue={value}设置了value值以后,但是defalult没有赋值上,输入框也没有显示
在 React 中,defaultValue 是一个非受控属性,而 value 是一个受控属性。这两个属性都可以用于设置 Input 组件的值,但是它们的工作方式有所不同。 value:这是一个受控属性,意味着输入框的值由 React 状态控制。每当状态…...

大模型开发如何把一段文字变成一组token?
在大模型开发中,将一段文字变成一组token通常称为"tokenization"(分词)。这是自然语言处理中的一个关键步骤,主要是将连续的文本划分成离散的单元(token),这些单元可以是单词、子词或…...

【MSYS】Windows Terminal 集成
Windows Terminal 集成 MSYS2安装在默认位置C:\msys64打开Windows Terminal打开JSON配置文件文件。 添加如下配置: "profiles": {"defaults": {},"list": [{"guid": "{71160544-14d8-4194-af25-d05feeac7233}"…...