C++:无序关联容器
遇到的问题,都有解决方案,希望我的博客能为您提供一点帮助。
一、无序关联容器概述
无序关联容器(如 unordered_set、unordered_map、unordered_multiset、unordered_multimap)基于 哈希表(Hash Table) 实现,与有序关联容器(如 set、map)的核心区别在于:
- 无序性:元素不按特定顺序存储,而是通过哈希函数快速定位。
- 时间复杂度:平均时间复杂度为 O(1)(理想情况下),最坏情况为 O(n)(哈希冲突严重时)。
1. 哈希表结构
无序关联容器的底层是一个 动态数组(桶数组),每个桶(Bucket)存储一个链表或另一哈希结构(如红黑树)来处理冲突。
- 桶数组:初始大小为默认值(如 8),随元素插入动态扩容。
- 哈希函数:将键(Key)映射为桶索引:
index = hash(key) % bucket_count();
结构示例:
Bucket 0: Key1 → Key2 → nullptr
Bucket 1: Key3 → nullptr
Bucket 2: nullptr
...
Bucket N: Key4 → Key5 → nullptr
2. 冲突解决策略
C++标准库采用 链地址法(Separate Chaining):
- 每个桶维护一个链表(单链表),哈希冲突时,元素追加到链表尾部。
- 示例:插入键
"apple"和"apply"(假设哈希值相同):桶数组索引:3 → 链表节点1: "apple" → 链表节点2: "apply"
3. 负载因子(Load Factor)与扩容
- 负载因子:
load_factor = size() / bucket_count()。 - 定义:
负载因子 = 元素数量 / 桶数量。 - 默认阈值:1.0(超过时触发扩容)。
- 扩容触发条件:当
load_factor > max_load_factor(默认 1.0)时,触发rehash。 - 扩容机制:
- 创建新的桶数组(大小通常为质数,约为原大小的两倍)。
- 对所有元素重新计算哈希值,并插入新桶。
- 旧桶链表节点被逐个移动到新桶(无需重建元素对象)。
4、无序容器的优势与使用场景
4.1. 优势
- 性能优势:哈希表在理想情况下(低冲突)提供常数时间操作,适合高频操作。
- 简化代码:无需定义关键字的比较运算符(仅需哈希函数和
==)。
4.2. 何时选择无序容器?
- 关键字类型无自然顺序(如 UUID、随机生成的数据)。
- 性能测试表明哈希技术能显著提升效率。
- 无需维护元素顺序,且希望减少插入/查找时间。
二、无序容器的操作与示例
1、支持的操作
无序容器提供与有序容器相同的接口,包括:
- 插入:
insert、emplace - 查找:
find、count - 删除:
erase - 遍历:迭代器(但顺序不确定)
2、STL中的无序容器接口
2.1. 容器定义
#include <unordered_set>
#include <unordered_map>// 定义示例
std::unordered_set<int> uset; // 唯一键集合
std::unordered_map<std::string, int> umap; // 键值对集合
std::unordered_multiset<int> umset; // 允许重复键的集合
std::unordered_multimap<std::string, int> ummap; // 允许重复键的键值对集合
2.2. 插入元素
-
insert:插入键或键值对,返回是否成功(对unordered_set/map)。 -
emplace:直接构造元素,避免拷贝。 -
operator[](仅unordered_map):若键不存在,插入默认值;存在则返回引用。
示例:
umap.insert({"apple", 5}); // 插入键值对
umap.emplace("banana", 3); // 直接构造元素
umap["orange"] = 8; // 使用operator[]插入或修改
2.3. 查找元素
-
find:返回指向元素的迭代器,未找到返回end()。 -
count:返回键的出现次数(对multi容器有效)。 -
equal_range:返回匹配键的范围迭代器对。
示例:
auto it = umap.find("apple");
if (it != umap.end()) {std::cout << "Found: " << it->second << std::endl;
}size_t cnt = ummap.count("apple"); // 统计键出现的次数
2.4. 删除元素
-
erase:通过迭代器、键或范围删除元素。 -
clear:清空所有元素。
示例:
umap.erase("apple"); // 删除键为"apple"的元素
auto it = umap.find("banana");
if (it != umap.end()) {umap.erase(it); // 通过迭代器删除
}
umap.clear(); // 清空容器
2.5. 桶管理接口
-
bucket_count():返回当前桶的数量。 -
bucket_size(n):返回第n个桶中的元素数量。 -
bucket(key):返回键所在的桶索引。
2.5.1. 桶接口
| 函数 | 作用 |
|---|---|
c.bucket_count() | 返回当前使用的桶数量(非最大值)。 |
c.max_bucket_count() | 返回容器支持的最大桶数量(受实现或内存限制)。 |
c.bucket_size(n) | 返回第 n 个桶中的元素数量(用于检查桶负载情况)。 |
c.bucket(k) | 返回键 k 所在的桶索引(用于定位元素分布)。 |
2.5.2. 桶迭代
| 类型/函数 | 作用 |
|---|---|
local_iterator | 遍历单个桶元素的迭代器(非 const 版本)。 |
const_local_iterator | 遍历单个桶元素的常量迭代器(不可修改元素)。 |
c.begin(n), c.end(n) | 返回第 n 个桶的起始和结束迭代器(用于遍历桶内元素)。 |
c.cbegin(n), c.cend(n) | 返回第 n 个桶的常量起始和结束迭代器。 |
size_t buckets = umap.bucket_count();
size_t bucket_idx = umap.bucket("apple");
size_t elements_in_bucket = umap.bucket_size(bucket_idx);
2.5.3. 哈希策略
| 函数/操作 | 作用 |
|---|---|
c.load_factor() | 返回当前平均每个桶的元素数量(size() / bucket_count())。 |
c.max_load_factor() | 返回容器试图维持的最大负载因子(默认通常为 1.0)。 |
c.rehash(n) | 重组哈希表,使桶数量至少为 n,并满足 bucket_count > size() / max_load_factor。 |
c.reserve(n) | 预留空间,使容器可保存 n 个元素而无需 rehash(自动调整桶数量)。 |
关键逻辑:
-
当
load_factor() > max_load_factor()时,容器自动增加桶数量(触发rehash)。 -
rehash(n)强制重组哈希表,适用于预知元素数量增长的场景。 -
reserve(n)等价于rehash(ceil(n / max_load_factor())),避免频繁重组。
三、自定义哈希与比较函数
1. 自定义哈希函数
为自定义类型作为键时,需提供哈希函数。哈希函数需满足:
-
相同输入产生相同哈希值。
-
不同输入尽可能产生不同哈希值(减少冲突)。
示例:
struct Person {std::string name;int age;
};// 自定义哈希函数
struct PersonHash {size_t operator()(const Person& p) const {return std::hash<std::string>()(p.name) ^ std::hash<int>()(p.age);}
};std::unordered_set<Person, PersonHash> person_set;
2. 自定义相等比较
默认使用operator==,可自定义相等谓词。
struct PersonEqual {bool operator()(const Person& a, const Person& b) const {return a.name == b.name && a.age == b.age;}
};std::unordered_set<Person, PersonHash, PersonEqual> person_set;
3. 使用std::hash组合(C++17)
通过组合多个哈希值,减少冲突概率。
template <typename T>
void hash_combine(size_t& seed, const T& val) {seed ^= std::hash<T>()(val) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}struct PersonHash {size_t operator()(const Person& p) const {size_t seed = 0;hash_combine(seed, p.name);hash_combine(seed, p.age);return seed;}
};
四、性能优化
1. 优化策略
-
预分配桶数量:减少重哈希次数。
std::unordered_map<std::string, int> umap(1000); // 初始1000个桶
-
调整最大负载因子:平衡内存与性能。
umap.max_load_factor(0.75); // 负载因子超过0.75时触发重哈希
-
预留空间:提前分配足够桶。
umap.reserve(5000); // 预留至少5000个元素的空间
2. 注意事项
-
哈希函数质量:差的哈希函数导致频繁冲突,性能下降。
-
键的不可变性:插入后修改键可能导致容器状态不一致。
-
迭代器失效:
-
插入可能触发重哈希,使所有迭代器失效。
-
删除仅使被删元素的迭代器失效。
-
五、对比有序关联容器
| 特性 | 无序关联容器 | 有序关联容器 |
|---|---|---|
| 底层实现 | 哈希表 | 红黑树 |
| 时间复杂度 | 平均O(1),最坏O(n) | 稳定O(log n) |
| 元素顺序 | 无序 | 按键升序排列 |
| 内存占用 | 较高(桶数组+链表) | 较低(树节点) |
| 自定义排序 | 仅哈希函数和相等比较 | 支持自定义比较函数 |
| 适用场景 | 快速查找,不关心顺序 | 需要有序遍历或范围查询 |
六、实际应用示例
1. 统计单词频率
std::unordered_map<std::string, int> word_count;
std::string word;
while (std::cin >> word) {++word_count[word];
}// 输出结果(无序)
for (const auto& [word, count] : word_count) {std::cout << word << ": " << count << std::endl;
}
2. 实现LRU缓存
template <typename Key, typename Value>
class LRUCache {
private:using List = std::list<std::pair<Key, Value>>;using Map = std::unordered_map<Key, typename List::iterator>;List lru_list;Map cache_map;size_t capacity;public:LRUCache(size_t cap) : capacity(cap) {}Value* get(const Key& key) {auto it = cache_map.find(key);if (it == cache_map.end()) return nullptr;// 移动访问项到链表头部lru_list.splice(lru_list.begin(), lru_list, it->second);return &(it->second->second);}void put(const Key& key, const Value& value) {auto it = cache_map.find(key);if (it != cache_map.end()) {// 更新现有项并移至头部lru_list.erase(it->second);}// 插入新项到头部lru_list.emplace_front(key, value);cache_map[key] = lru_list.begin();// 超出容量则移除尾部if (cache_map.size() > capacity) {cache_map.erase(lru_list.back().first);lru_list.pop_back();}}
};相关文章:
C++:无序关联容器
遇到的问题,都有解决方案,希望我的博客能为您提供一点帮助。 一、无序关联容器概述 无序关联容器(如 unordered_set、unordered_map、unordered_multiset、unordered_multimap)基于 哈希表(Hash Table)…...
3.27学习总结 算法题
自己用c语言做的,不尽如意 后面看了题解,用的是c,其中string 变量和字符串拼接感觉比c方便好多,可以用更少的代码实现更好的效果,打算之后去学习c,用c写算法。 递归,不断输入字符,…...
案例分享|树莓派媒体播放器,重构商场广告的“黄金三秒”
研究显示,与传统户外广告相比,数字户外广告在消费者心中的记忆率提高了17%,而动态户外广告更是能提升16%的销售业绩,整体广告效率提升了17%。这一显著优势,使得越来越多资源和技术流入数字广告行业。 户外裸眼3D广告 无…...
Redisson - 分布式锁和同步器
文章目录 锁(Lock)公平锁(Fair Lock)联锁(MultiLock)红锁(RedLock) 【已废弃】读写锁(ReadWriteLock)信号量(Semaphore)可过期许可信号…...
Zustand 状态管理:从入门到实践
Zustand 状态管理:从入门到实践 Zustand 是一个轻量、快速且灵活的 React 状态管理库。它基于 Hooks API,提供了简洁的接口来创建和使用状态,同时易于扩展和优化。本文将通过一个 TODO 应用实例带你快速入门 Zustand,并探讨其核心…...
[RITSEC CTF 2025] Crypto
这个忘打了,难度不小。 Alien Encryption 101 一个很小的RSA,略 Cuwves 2 Electric Boogaloo 已知p,在p^2下的两个椭圆曲线的j不变量,直接用函数 Mothership AES_CBC加密给出密文和IV,通过调整IV来修改明文 import base64 …...
算法250327题目
1114: 4006 AB问题 题目描述 给定两个整数A和B,其表示形式是:从个位开始,每三位数用逗号,隔开。 现在请计算AB的结果,并以正常形式输出。 输入 输入包含多组数据,每组数据占一行,由两个整数A和B组成&am…...
PGP实现简单加密教程
模拟情景: 假设001和002两位同学的电脑上都安装了PGP,现在两人需要进行加密通讯。 一、创建密钥 1.新建密钥,输入名称和邮箱,输入8位口令,根据指示完成。 2.将其添加到主密钥,鼠标右击出现选项。 这里出…...
7.8 窗体间传递数据
版权声明:本文为博主原创文章,转载请在显著位置标明本文出处以及作者网名,未经作者允许不得用于商业目的 当项目中有多个窗体时(在本节中为两个窗体:Form1和Form2),窗体间传递数据有以下几种方…...
一文了解 MCP Server:AI 工具与外部世界的桥梁
引言 随着大语言模型(LLM)的普及与 AI Agent 的爆发,Anthropic 于 2024 年底提出并开源的 Model Context Protocol(MCP,模型上下文协议)成为构建智能体系统的关键基石之一。本文将结合最新的实战经验&#…...
【redis】集群 数据分片算法:哈希求余、一致性哈希、哈希槽分区算法
文章目录 什么是集群数据分片算法哈希求余分片搬运 一致性哈希扩容 哈希槽分区算法扩容相关问题 什么是集群 广义的集群,只要你是多个机器,构成了分布式系统,都可以称为是一个“集群” 前面的“主从结构”和“哨兵模式”可以称为是“广义的…...
基于Springboot的网上订餐系统 【源码】+【PPT】+【开题报告】+【论文】
网上订餐系统是一个基于Java语言和Spring Boot框架开发的Web应用,旨在为用户和管理员提供一个便捷的订餐平台。该系统通过简化餐饮订购和管理流程,为用户提供快速、高效的在线订餐体验,同时也为管理员提供完善的后台管理功能,帮助…...
Redis常见面试问题汇总
Redis 面试笔记整理 一、Redis 基础知识1. Redis 概述Redis 是什么?主要特点有哪些?Redis 和 Memcached 的区别是什么?Redis 是单线程还是多线程?为什么单线程还能高效?Redis 6.0 之后的多线程模型是怎样的?…...
【redis】集群 如何搭建集群详解
文章目录 集群搭建1. 创建目录和配置2. 编写 docker-compose.yml完整配置文件 3. 启动容器4. 构建集群超时 集群搭建 基于 docker 在我们云服务器上搭建出一个 redis 集群出来 当前节点,主要是因为我们只有一个云服务器,搞分布式系统,就比较…...
NLP高频面试题(二十)——flash attention原理
FlashAttention是一种针对Transformer模型中自注意力机制的优化算法,旨在提高计算效率并降低内存占用,特别适用于处理长序列任务。 在Transformer架构中,自注意力机制的计算复杂度和内存需求随着序列长度的平方增长。这意味着当处理较长序列时…...
飞牛NAS本地部署小雅Alist结合内网穿透实现跨地域远程在线访问观影
文章目录 前言1. VMware安装飞牛云(fnOS)1.1 打开VMware创建虚拟机1.3 初始化系统 2. 飞牛云搭建小雅Alist3. 公网远程访问小雅Alist3.1 安装Cpolar内网穿透3.2 创建远程连接公网地址 4. 固定Alist小雅公网地址 前言 嘿,小伙伴们,…...
Episode, time step, batch, epoch
1. Episode(回合) 回合(episode)表示智能体从开始执行任务到完成任务(例如成功到达目标或触发失败条件)的全过程。 例如,如果我们训练一个四足机器人走到一个目标点,一个回合就是从…...
Linux版本控制器Git【Ubuntu系统】
文章目录 **前言**一、版本控制器二、Git 简史三、安装 Git四、 在 Gitee/Github 创建项目五、三板斧1、git add 命令2、git commit 命令3、git push 命令 六、其他1、git pull 命令2、git log 命令3、git reflog 命令4、git stash 命令 七、.ignore 文件1、为什么使用 .gitign…...
browser-use 库网页元素点击测试工具
目录 代码代码解释输出结果 代码 import asyncio import jsonfrom browser_use.browser.browser import Browser, BrowserConfig from browser_use.dom.views import DOMBaseNode, DOMElementNode, DOMTextNode from browser_use.utils import time_execution_syncclass Eleme…...
Vue 中使用 ECharts
在 Vue 中使用 ECharts 主要分为以下步骤,结合代码示例详细说明: 1. 安装 ECharts 通过 npm 或 yarn 安装 ECharts: npm install echarts --save # 或 yarn add echarts2. 基础使用(完整引入) 在 Vue 组件中使用 &…...
Spring AI + DeepSeek 构建大模型应用 Demo
Spring AI + DeepSeek 构建大模型应用 Demo 下面我将展示如何使用 Spring AI 框架结合 DeepSeek 的大模型能力构建一个简单的 AI 应用。 1. 环境准备 首先确保你已安装: JDK 17+Maven 3.6+Spring Boot 3.2+2. 创建 Spring Boot 项目 使用 Spring Initializr 创建项目,添加…...
解决GitLab无法拉取项目
1、验证 SSH 密钥是否已生成 ls ~/.ssh/ 如果看到类似 id_rsa 和 id_rsa.pub 的文件,则说明已存在 SSH 密钥。 避免麻烦,铲掉重来最方便。 如果没有,请生成新的 SSH 密钥: ssh-keygen -t rsa -b 4096 -C "your_emailexam…...
POSIX 线程取消与资源清理完全指南
POSIX 线程取消与资源清理完全指南 引言:为什么需要线程取消机制? 在多线程编程中,优雅地终止线程并确保资源释放是开发者面临的重要挑战。直接终止线程可能导致内存泄漏、文件未关闭等问题。POSIX 线程库提供了一套完整的线程取消和清理机…...
FPGA学习篇——Verilog学习之寄存器的实现
1 寄存器理论 这里在常见的寄存器种加了一个复位信号sys_rst_n。(_n后缀表示复位信号低电平有效,无这个后缀的则表示高电平有效) 这里规定在时钟的上升沿有效,只有当时钟的上升沿来临时,输出out 才会改变,…...
Cursor异常问题全解析-无限使用
title: Cursor异常问题全解析无限使用 tags: cursor categories:aiai编程 mathjax: true description: Cursor异常问题全解析与解决方案大全 abbrlink: 64908bd0 date: 2025-03-19 14:48:32 🤖 Assistant 🚨 Cursor异常问题全解析与解决方案大全 &…...
【VUE】ant design vue实现表格table上下拖拽排序
适合版本:ant design vue 1.7.8 实现效果: 代码: <template><div class"table-container"><a-table:columns"columns":dataSource"tableData":rowKey"record > record.id":row…...
Vue实现动态数据透视表(交叉表)
需求:需要根据前端选择的横维度、竖维度、值去生成一个动态的表格,然后把交叉的值放入到对应的横维度和竖维度之下,其实就是excel里面的数据透视表功能,查询交叉语句为sql语句。 实现页面: 选择一下横维度、竖维度、值之后点击查…...
推荐《人工智能算法》卷1、卷2和卷3 合集3本书(附pdf电子书下载)
今天,咱们就一同深入探讨人工智能算法的卷1、卷2和卷3,看看它们各自蕴含着怎样的奥秘,并且附上各自的pdf电子版免费下载地址。 《人工智能算法(卷1):基础算法》 下载地址:https://www.panziye…...
元宇宙浪潮下,数字孪生如何“乘风破浪”?
在当今科技飞速发展的时代,元宇宙的概念如同一颗璀璨的新星,吸引了全球的目光。元宇宙被描绘为一个平行于现实世界、又与现实世界相互影响且始终在线的虚拟空间,它整合了多种前沿技术,为人们带来沉浸式的交互体验。而数字孪生&…...
WPF 附加属性
在WPF(Windows Presentation Foundation)中,附加属性(Attached Properties)是一种特殊的依赖属性机制,它允许父元素为子元素提供额外的属性支持。这种特性特别适用于布局系统、输入处理和其他需要跨多个控件…...
