STL关联式容器使用注意、概念总结
引入
继上文 STL序列式容器使用注意、概念总结 继续总结关联式容器的概念以及一些使用事项。
关联式容器与容器适配器
基础容器
STL 中的关联式底层容器:RB tree, hash table,可以作为其他容器的底层结构。
1.RB tree
RB tree红黑树这玩意儿可是重量级的,书中从基本的树概念讲起,到二叉树、BST、AVL,然后才到RB tree。简单来说,AVL是高度极其平衡的BST,任意节点的左右子树高度差不超过1。那为什么还要RB tree呢?因为实践证明AVL为了保持其平衡性需要经常进行旋转操作,影响性能。而RB tree减轻了对平衡的要求,根据它的定义(我实在没能背下来,也不知道发明它的人是怎么想的,牛B),任意节点的左右子树高度差有可能超过1,由此带来的好处是不那么频繁的旋转操作。 在实际应用中,RB tree的表现比AVL更好。- 总结起来
RB tree就是一个虽然不是严格平衡但也非常平衡的二叉搜索树,插入、删除、查找等操作都能保持在 \(O(log \space n)\) 的时间复杂度。 - 它的迭代器类型为 Bidirectional Iterator 双向迭代器。
2.hash table
- 真正的哈希表,目的是使得各项操作如插入、删除、查找的均摊时间开销是 \(O(1)\)。
- 哈希函数:将一个元素映射到一个“大小可接受的索引”。随着 C++ 标准的发展,现在几乎所有内置类型都自带了哈希函数,我遇到过
pair和自定义类型需要自定义哈希函数(自定义方法见下文unordered_map)。 - 哈希碰撞:哈希函数可能将不同的元素映射到相同的位置。
- 哈希碰撞的解决方法:
- 线性探测:存在主集团问题,即碰撞概率太大,大部分元素集中在一起,导致插入一个元素经常需要遍历经过这些集中在一起的元素才能找到一个空位,成本太高。
- 二次探测:消除了线性探测存在的主集团问题,但仍存在次集团问题,即若两个元素的哈希位置相同,则探测位置也相同,第二个元素需要探测第一个元素曾经走过的所有位置。
- 拉链法:表格存放的不再是
value,每一个位置都是一个链表指针,将映射到相同位置的元素放在同一条链表中。
- 负载系数:元素个数除以表格大小(表格是存放
value的地方)。除了拉链法,其他方法的负载系数都在 0-1 之间。 - STL 采用拉链法实现,自带的
vector和自维护的list(没有使用已经实现的双向循环链表list)。相信大家都用过这种方法,它的一大局限是映射到同一个位置的元素过多,链表会变得很长,此时查找等操作的时间开销就很大了。STL 实现的哈希表有一个表格重整的机制,专门解决这种情况。 - 采用拉链法实现哈希表,表格的大小一般设置为质数以减少碰撞,STL 内置了 28 个质数。
- 它的迭代器
Forward Iterator前向迭代器,只支持自增操作(++)。
容器适配器
以某种容器作为底层结构,改变其接口,使之符合某种特性。关联式容器适配器有:
- 以
RB tree为底层结构的map和set。 - 以
hash table为底层结构的unordered_map和unordered_set。
这 4 种容器适配器都不允许 key 重复,它们都有对应的允许 key 重复的版本:multimap, multiset, unordered_multimap 和 unordered_multiset。
这里不讲实现细节,讲讲一些注意事项和使用方法。
通过下标访问
map时,一定要确保key已存在,否则将插入一个新建的key-value对,key是指定的,value是类型的默认值,如下代码所示:map<int, int> m;
cout << boolalpha << (m.find(2) == m.end()) << endl; // true
if (m[2] > 0) { // do something } // if 里潜在地插入了 m[2] = 0
cout << boolalpha << (m.find(2) == m.end()) << endl; // false
除非你本身需要这种特点,否则要特别注意喔。
set集合,key就是value,由于key不可变,所以我们不能通过set的迭代器更改其元素值,其迭代器是一种常量迭代器。map和set会对插入的元素进行排序,因为底层的RB tree也是BST,对BST进行中序遍历即可得到有序序列。正如它们的名字所说的那样,
unordered_map和unordered_set不会对插入的元素排序。如何为
unordered_map自定义哈希函数和key比较函数呢?下面给出两个示例,一个针对自定义类类型,另一个针对pair。核心思想是重载函数调用符。一、为
pair自定义哈希函数和key比较函数其实
pair本身重载了比较函数,不自己定义也可以。#include <unordered_map>
using namespace std; // 哈希函数
struct MyKeyHash
{
size_t operator()(const pair<int, int>& p) const {
// 第一种写法里第一个()是实例化一个hash<int>类对象
// 第二个()是像函数一样使用该对象
// 因为它重载了函数调用符
// return hash<int>()(p.first) ^ hash<int>()(p.second); // 这第二种写法C++11开始支持
// {}表示列表初始化
// 类似vector<int>{1, 2, 3}
return hash<int>{}(p.first) ^ hash<int>{}(p.second); // 异或一下
}
}; // key比较函数
struct MyKeyEqual
{
bool operator() (const pair<int, int>& key1, const pair<int, int>& key2) const {
return key1.first == key2.first && key1.second == key2.second;
}
}; int main()
{
unordered_map<pair<int, int>, int, MyKeyHash, MyKeyEqual> um;
// unordered_map<pair<int, int>, int, MyKeyHash> um; // 其实pair本身重载了比较函数,不自己定义也可以。
um.insert(pair<pair<int, int>, int>(pair<int, int>(1, 1), 2)); // 这句长到头皮发麻
return 0;
}
哈希函数通过异或实现,网上说这种方法在数据量较大时发生碰撞的概率很大,导致性能很差。有位大佬的博客说他在《C++ 标准库》第二版这本书里找到了正解,用过都说好的哈希函数,链接在这:C++ pair 作为 unordered_map unordered_set 的键值。
二、为自定义类类型自定义哈希函数和
key比较函数#include <unordered_map>
#include <string>
using namespace std; // 自定义一个Person类型
class Person {
private:
int m_id; // 身份证
int m_age; // 年龄
string m_name; // 姓名
// 其他成员
public:
int getId() const { return m_id; }
int getAge() const { return m_age; }
string getName() const { return m_name; } Person() = default;
Person(int id, int age, string name): m_id(id), m_age(age), m_name(name) {}
}; struct MyKeyHash
{
size_t operator()(const Person& p) const {
// 这里只使用一个人的年龄和姓名进行哈希映射
// return hash<int>{}(p.getAge()) ^ hash<string>{}(p.getName());
return hash<int>()(p.getAge()) ^ hash<string>()(p.getName());
}
}; struct MyKeyEqual
{
bool operator() (const Person& p1, const Person& p2) const {
// 当且仅当两个人的身证份一致时才认为是同一个人
return p1.getId() == p2.getId();
}
}; int main()
{
unordered_map<Person, int, MyKeyHash, MyKeyEqual> um;
um.insert(pair<Person, int>(Person(), 1));
return 0;
}
最后
如果你有疑惑,欢迎评论,我会尽可能回复!
如果本文对你有帮助,点个赞吧,这是我坚持的动力!
STL关联式容器使用注意、概念总结的更多相关文章
- STL——关联式容器
一.关联式容器 标准的STL关联式容器分为set(集合)/map(映射表)两大类,以及这两大类的衍生体multiset(多键集合)和 multimap(多键映射表).这些容器的底层机制均以RB-tre ...
- 关联式容器(associative containers)
关联式容器(associative containers) 根据数据在容器中的排列特性,容器可分为序列式(sequence)和关联式(associative)两种. 标准的STL关联式容器分为set( ...
- STL源码分析读书笔记--第5章--关联式容器
1.关联式容器的概念 上一篇文章讲序列式容器,序列式容器的概念与关联式容器相对,不提供按序索引.它分为set和map两大类,这两大类各自有各自的衍生体multiset和multimap,的底层机制都是 ...
- STL学习笔记--关联式容器
关联式容器依据特定的排序准则,自动为其元素排序.缺省情况下以operator<进行比较.set multiset map multimap是一种非线性的树结构,具体的说是采用一种比较高效的特殊平 ...
- C++ 容器:顺序性容器、关联式容器和容器适配器
什么是容器 首先,我们必须理解一下什么是容器,在C++ 中容器被定义为:在数据存储上,有一种对象类型,它可以持有其它对象或指向其它对像的指针,这种对象类型就叫做容器.很简单,容器就是保存其它对象的对象 ...
- STL序列式容器学习总结
STL序列式容器学习总结 参考资料:<STL源码剖析> 参考网址: Vector: http://www.cnblogs.com/zhonghuasong/p/5975979.html L ...
- C++关联式容器的排序准则
stl中set和map为关联式容器,会根据排序准将元素自动排序.原型如下: template<class _Kty, class _Pr = less<_Kty>, class _A ...
- STL关联式容器之map和multimap
一,map和multimap的概念 1.map和multimap的基本知识 map是标准的关联式容器,一个map是一个键值对序列,即(key,value)对.它提供基于key的快速检索能力. map中 ...
- STL关联式容器之set\map ----以STL源码为例
关联式容器的特征:所用元素都会根据元素的键值自动被排序. set STL 中的关联式容器低层数据结构为红黑树,其功能都是调用低层数据结构中提供的相应接口. set元的元素不会像map那样同时拥有键(k ...
- Map(关联式容器)
map是一类关联式容器,自动建立Key - Value的对应 , key 和 Value可以是任意你需要的类型.下面介绍 map 中一些常用的函数: 一.map 中的 begin 和 end 函数 m ...
随机推荐
- 【lwip】09-IPv4协议&超全源码实现分析
目录 前言 9.1 IP协议简述 9.2 IP地址分类 9.2.1 私有地址 9.2.2 受限广播地址 9.2.3 直接广播地址 9.2.4 多播地址 9.2.5 环回地址 9.2.6 本地链路地址 ...
- Sprint产品待办列表的优先级要怎么排?
在梳理产品待办事项列表的过程中,产品负责人需要先做优先级排列,保证我们 在一定的时间盒内能够交付需要优先级最高.最具价值的用户故事. 那这个用户故事的优先级要怎么排列,我们怎样选择用户故事的实现顺序? ...
- Day1:Markdown文本基础操作
Markdown学习 标题 一级标题 输入:#+空格+标题名字 :快捷键(Ctrl+1~6) 二级标题 输入:##+空格+标题名字 为二级标题 依次加#(最多六级标题) 三级标题 四级标题 字体 He ...
- 聊聊FASTER和进程内混合缓存
最近有一个朋友问我这样一个问题: 我的业务依赖一些数据,因为数据库访问慢,我把它放在Redis里面,不过还是太慢了,有什么其它的方案吗? 其实这个问题比较简单的是吧?Redis其实属于网络存储,我对照 ...
- 基于SpERT的中文关系抽取
SpERT_chinese 基于论文SpERT: "Span-based Entity and Relation Transformer"的中文关系抽取,同时抽取实体.实体类别和关 ...
- 【Java并发010】使用层面:发令枪CountDownLatch全解析
一.前言 CountDownLatch是在java1.5被引入,存在于java.util.cucurrent包中,跟它一起被引入的工具类还有CyclicBarrier.Semaphore.concur ...
- day24-服务器端渲染技术02
服务器端渲染技术02 11.EL表达式 11.1EL表达式介绍 EL表达式全称:Expression Language,是表达式语言 EL表达式主要是代替jsp页面的表达式脚本 EL表达式输出数据时, ...
- 9V,12V输入充3.7V单节锂电池电路和芯片
锂电池充电管理电路中,普遍常用使用最多的的如PW4054这种的线性降压充电管理芯片,特点就是外围极简洁,但是只能支持USB口的输入5V了.当然也有稍微高点的PW4065,输入电压范围是4.7V-8V的 ...
- Python编程规范之PEP8
Python编程规范-PEP8 PEP是 Python Enhancement Proposal 的缩写. 英文链接: https://legacy.python.org/dev/peps/pep-0 ...
- Qwt开发笔记(二):Qwt基础框架介绍、折线图介绍、折线图Demo以及代码详解
前言 QWT开发笔记系列整理集合,这是目前使用最为广泛的Qt图表类(Qt的QWidget代码方向只有QtCharts,Qwt,QCustomPlot),使用多年,系统性的整理,本系列旨在系统解说并 ...