stl 基于哈希的map c++_【C++】一文带你入门 STL
一 STL 组成
graph LR A[STL] --- B[容器 container] A --- C[配接器 adapter] A --- D[迭代器 iterator] A --- E[仿函数 function] A --- F[算法 algorithm] A --- G[空间配置器 allocator]
二 常用容器
![](/assets/blank.gif)
容器简介
下面我们来简单看一下这些容器的常用接口的使用,并分析其复杂度
vector
![](/assets/blank.gif)
list
![](/assets/blank.gif)
deque
![](/assets/blank.gif)
stack
![](/assets/blank.gif)
queue
![](/assets/blank.gif)
priority_queue
![](/assets/blank.gif)
set & multiset
![](/assets/blank.gif)
map & multimap
![](/assets/blank.gif)
unordered_map & unordered_multimap
unordered_map | |
---|---|
底层实现: | 动态数组 + 链表(红黑树) |
查找: |
find()
|
计数: |
count()
|
指定插入: |
insert()
|
指定删除: |
erase()
|
随机访问 |
operator[]
|
由于底层实现哈希表的时候,会有负载因子的存在,所以可以认为上述操作的复杂度都为 O(1)
如果哈希冲突比较多,可以采用 动态数组 + 红黑树实现,复杂度最多为 O(logn)
因此,哈希表的优势为 O(1)
的查找;这是一种以空间换时间的策略。
容器选择
不知道使用什么容器时,优先选择
vector
如果不需要频繁的任意位置插入,需要支持随机访问,选择
vector
如果不需要随机访问,需要频繁的插入,选择
list
如果需要频繁的对头/尾操作,选择
deque
如果需要高效的查找,但对内存没有限制,可以选择
unordered_map
;如果对内存有限制,可以选择set/map
deque
在 STL 中作为stack
和queue
底层实现。其优势为:劣势为:不适合遍历,因为在遍历时,
deque
的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下- 相比于
vector
:头部插入和删除以及扩容时,效率更高,因为不需要搬大量的元素 - 相比于
list
:底层空间更为连续,空间利用率更高
- 相比于
三 迭代器
1. 迭代器种类
迭代器种类一节摘自: Stl整理 (https://wu-kan.cn/_posts/2019-04-20-STL%E6%95%B4%E7%90%86/#%E7%AE%97%E6%B3%95%E6%A6%82%E8%A7%88)
C++中,一图解释五类迭代器之间的继承关系:
graph LRA[输入迭代器]B[输出迭代器]C[前向迭代器]D[双向迭代器]E[随机访问迭代器]
A --> CB --> CC --> DD --> E
输入迭代器(input iterator)
只读,不写;单遍扫描,只能递增。
输入迭代器只支持顺序访问,通常用于读取序列中的元素。对于一个输入迭代器it
,*it++
保证是有效的,但递增它可能导致所有其他指向流的迭代器失效。其结果就是,不保证其状态可以保存下来并用来访问元素。因此,输入迭代器只能用于单遍扫描的算法。支持如下操作:
- 相等(
==
)、不等(!=
)比较。 - 用于推进迭代器的前置和后置递增(
++
)。 - 解引用(
*
),只能出现在赋值运算的右侧;箭头运算符(->
),it->member
等价于(*it).member
。
算法find
要求输入迭代器;而类型istream_iterator
是一种输入迭代器。
输出迭代器(output iterator)
只写,不读;单遍扫描,只能递增。可以看作输入迭代器功能上的补集。
只能向解引用的输出迭代器赋值一次,类似输入迭代器,输出迭代器也只能用于单遍扫描的算法,通常用作算法的目标位置。支持如下操作:
- 用于推进迭代器的前置和后置递增(
++
)。 - 解引用(
*
),只能出现在赋值运算的左侧。
算法fill
要求输出迭代器;而类型ostream_iterator
是一种输出迭代器。
前向迭代器(forward iterator)
可读写;多遍扫描,只能递增。
可以读写元素,只能在序列中沿着一个方向移动,支持所有输入迭代器和输出迭代器的操作,而且可以多次读写同一个元素。因此,可以保存前向迭代器的状态,使用前向迭代器的算法可以进行多遍扫描。
算法replace
要求前向迭代器;容器forward_list
上的迭代器是前向迭代器。
双向迭代器(bidrectional iterator)
可读写;多遍扫描,能递增递减。
可正向/反向读写序列中的元素。除支持所有前向迭代器的操作之外,双向迭代器还支持前置和后置递减(--
)。
算法reverse
要求双向迭代器;除了forward_list
之外,其他标准库容器上的迭代器都是双向迭代器。
随机访问迭代器(random-access iterator)
可读写;多遍扫描,支持全部迭代器运算。
提供在常数时间内访问序列中任意元素的能力,除支持双向迭代器的所有操作外,还支持:
- 用于比较两个迭代器的相对位置(
>
、<
、>=
、<=
)。 - 迭代器和一个整数值的加减运算(
+
、+=
、-
、-=
),计算结果迭代器在序列中前进或后退给定整数后的迭代器。 - 迭代器之间的减法(
-
),得到两个迭代器之间的距离。 - 下标运算符(
[]
),it[n]
与*(it+n)
等价。
算法sort
要求随机访问迭代器;容器vector
上的迭代器和用于访问内置数组的指针是随机访问迭代器。
2. 迭代器失效
图片来源 cppreference
![](/assets/blank.gif)
3. 迭代器实现
以下都是迭代器的简单实现,要看对容器的完整实现,请去我的 Github,链接如下:
https://github.com/hairrrrr/Cpp-Primer
记得留下你的 star 哟~
deque
简单实现:
template<class T,...>struct __deque_iterator{ ... T* cur; T* first; T* last; map_pointer node;//map_pointer 等价于 T**}
//当迭代器处于当前连续空间边缘的位置时,如果继续遍历,就需要跳跃到其它的连续空间中,该函数可用来实现此功能void set_node(map_pointer new_node){ node = new_node;//记录新的连续空间在 map 数组中的位置 first = *new_node; //更新 first 指针 //更新 last 指针,difference_type(buffer_size())表示每段连续空间的长度 last = first + difference_type(buffer_size());}//重载 * 运算符reference operator*() const{return *cur;}pointer operator->() const{return &(operator *());}//重载前置 ++ 运算符self & operator++(){ ++cur; //处理 cur 处于连续空间边缘的特殊情况 if(cur == last){ //调用该函数,将迭代器跳跃到下一个连续空间中 set_node(node+1); //对 cur 重新赋值 cur = first; } return *this;}//重置前置 -- 运算符self& operator--(){ //如果 cur 位于连续空间边缘,则先将迭代器跳跃到前一个连续空间中 if(cur == first){ set_node(node-1); cur == last; } --cur; return *this;}
list
/** * ListNode: 链表的节点类型 */template<class T>struct ListNode{ T _value; ListNode* _next; ListNode* _prev;
ListNode(const T& val = T()) :_value(val) ,_next(nullptr) ,_prev(nullptr) {}};
/** * ListIterator: 链表的迭代器 * 类类型的迭代器不是指针,而是一个类。 * 各种操作其实本质是操作 ListNode 中的某个成员 */template<class T, class Ref, class Ptr>struct ListIterator{ typedef ListNode Node;typedef ListIterator Self; ListIterator(Node* node) :_node(node) {} Ref operator*() {return _node->_value; }/** * -> 当作单目运算符处理 * 类类型中的元素访问模式:ListNode->_val->某一个元素 * 中间的 ->_val 被编译器优化 */ Ptr operator->() {return &_node->_value; } Self& operator++() { _node = _node->_next;return *this; } Self& operator--() { _node = _node->_prev;return *this; }bool operator!=(const Self& it) {return _node != it._node; } Node* _node;};
因为 map & set & multimap & multiset 底层都是用红黑树实现的,因此,我们有必要看一下红黑树的迭代器如何实现。
红黑树
template<class Value>struct RBNode{ typedef RBNode* Ptr_RBNode; Value _data; Color _color; Ptr_RBNode _left; Ptr_RBNode _right; Ptr_RBNode _parent; RBNode(const Value& data = Value()) :_data(data) ,_color(RED) // 节点颜色默认初始化为 red ,_left(nullptr) ,_right(nullptr) ,_parent(nullptr) {}};template<class Value>struct RBIterator{typedef RBNode Node;typedef RBIterator Self; Node* _node; RBIterator(Node* node) :_node(node) {} Value& operator*() {return _node->_data; } Value* operator->() {return &_node->_data; }bool operator==(const Self& it) {return _node == it._node; }bool operator!=(const Self& it) {return _node != it._node; } Self& operator++() {// 如果当前节点有右节点if (_node->_right) { _node = _node->_right;// 让 _node 设为最右节点while (_node->_left) _node = _node->_left; }// 向上回溯,如果当前节点为父节点的右节点,继续向上回溯else { Node* parent = _node->_parent;while (_node == parent->_right) { _node = parent; parent = parent->_parent; }// 当 parent 为 _header ,_node 为整个树的根节点时,// 一定会退出上面的 while 循环,此时应该将 _node 置为 _header// 如果 _node 不在父节点的左侧,也应该单独执行一次,将 _node 指向下一次访问的节点/*_node = parent;*/// 上面的写法有一个问题,如果整个树只有一个节点。_header->right == _node// _node 会走到 _header, parent 走到 _node 此时退出循环// _node 再被赋值为 _node , 所以迭代器的遍历会陷入循环if (_node->_right != parent) _node = parent; }return *this; }};
map 和 set 的迭代器只是对红黑树的迭代器进行了封装,然后给出了 begin()
和 end()
方法。
哈希表
template<class K, class V, class keyOfValue, class HashFun>struct HashIterator{ typedef HashNode Node;typedef HashTable _HashTable;typedef HashIterator Self; Node* _node; _HashTable* _ht; HashIterator( Node* node, _HashTable* ht) :_node(node) ,_ht(ht) {} V& operator*() const {return _node->_value; } V* operator->() const {return &_node->_value; }bool operator!=(const Self& it) const {return _node != it._node; } Self& operator++() {// 如果 _node._next 存在if (_node->_next != nullptr) { _node = _node->_next; }// 在哈希表中向后寻找非空链表else { keyOfValue kov; HashFun hf;int idx = hf(kov(_node->_value)) % _ht->_table.size(); idx++;for (; idx _table.size(); idx++) {if (_ht->_table[idx]) { _node = _ht->_table[idx];break; } }// 哈希表已遍历完if (idx == _ht->_table.size()) _node = nullptr;return *this; } }};
四 仿函数
priority_queue
默认是一个“大根堆”,使用如下代码测试:
int main(void){ priority_queue<int> pq;
pq.push(9); pq.push(5); pq.push(2); pq.push(7);
while (!pq.empty()) { cout <endl; pq.pop(); }}
输出:
9752
如果我们想要堆中最小的元素,也就是建立一个“小根堆”,可以通过这种方式定义 pq:
priority_queue<int, vector<int>, greater<int>> pq;
第二个参数表示 priority_queue
内部的实现方式,第三个参数定义了比较规则
我们也可以自己来定义比较规则:
template<class T>struct MyGreater : public binary_functionbool>{bool operator()(const T& lhs, const T& rhs){return lhs > rhs; }};
然后通过如下方式调用:
priority_queue<int, vector<int>, MyGreater<int>> pq;
五 空间配置器
未完待续。。。
六 算法
未完待续。。。
参考文章:
C++ STL deque容器底层实现原理(深度剖析)
http://c.biancheng.net/view/6908.html
竞赛常用STL容器详解
https://blog.csdn.net/weixin_43844677/article/details/104902417
Stl整理
https://wu-kan.cn/_posts/2019-04-20-STL%E6%95%B4%E7%90%86/#%E7%AE%97%E6%B3%95%E6%A6%82%E8%A7%88
关注公众号 不会编程的程序圆,看更多干货
C++ 学习代码仓库:https://github.com/hairrrrr/Cpp-Primer
C 语言学习代码仓库:https://github.com/hairrrrr/C-CrashCourse
公众号ID:不会编程的程序圆学习编程,看更多干货
你们的在看就是对程序圆最大的鼓励!
stl 基于哈希的map c++_【C++】一文带你入门 STL相关推荐
- stl 基于哈希的map c++_关于哈希表,你该了解这些!
(给算法爱好者加星标,修炼编程内功) 来源:代码随想录(本文来自作者投稿) 哈希表 首先什么是 哈希表,哈希表(英文名字为Hash table,国内也有一些算法书籍翻译为散列表,大家看到这两个名称知道 ...
- BCrypt加密怎么存入数据库_松哥手把手带你入门 Spring Security,别再问密码怎么解密了...
因为之前有小伙伴在松哥群里讨论如何给微人事的密码解密,我看到聊天记录后就惊呆了. 无论如何我也得写一篇文章,带大家入门 Spring Security!当我们在一个项目中引入 Spring Secur ...
- ntnub原理怎么看_老电工由浅入深带你入门学PLC的工作原理和梯形图的编程规则...
PLC编程怎么学?很难吗?工控小白怎么入门学习PLC?需要为学习PLC编程做哪些准备?学习PLC编程时,前期一定要积累相关的理论知识,有了一定的基础,基础打扎实之后就是多练习了.今天推荐的重点,是关于 ...
- seo技术_基础知识_网站pr值的意义_日思663.带你入门SEO基础知识
2019/9/6 这篇文章来自36氪产品团队一次内部分享,按照惯例记录下来,也分享给大家~ 本文937字,阅读约9分钟 从上上周开始,产品团队每周都会请一位小伙伴给大家做分享,主题不限,以自己擅长或感 ...
- sql查询每科成绩的最高分_数据分析SQL查询:一文带你入门到掌握
[背景介绍] 在一家知名电商企业的BI部门实习四个多月,岗位为数据分析.日常工作中打交道最多的就是SQL和EXCEL,在实习之前SQL技能只会简单的增删改查语句,第一周实习经理甩了一份业务常见绩效取数 ...
- notebook pip install 只有星号_每日一点,带你入门Python-星号拆包
星号(*)解包 今天我们来说说在python中经常使用的解包语法,这是python简洁语法的体现之一.在日常处理集合数据时非常有用.按照惯例,我们使用一个个尽可能小的例子来说明. 序列解包到变量 nu ...
- java etl工具_一文带你入门ETL工具-datax的简单使用
什么是ETL? ETL负责将分布的.异构数据源中的数据如关系数据.平面数据文件等抽取到临时中间层后进行清洗.转换.集成,最后加载到数据仓库或数据集市中,成为联机分析处理.数据挖掘的基础. ETL是数据 ...
- HashMap - 基于哈希表和 Map 接口的键值对利器 (JDK 1.7)
HashMap 的一些整理: (JDK 1.7) 基于哈希表的Map接口的非同步实现,定义了键映射到值的规则 此实现提供所有可选的映射操作,并允许使用null值和null键 此实现假定哈希函数将元素适 ...
- mysql做kv数据库_从零开始写KV数据库:基于哈希索引
前言 新的KV数据库层出不穷,我们经常听说的KV数据库如RocksDb.Hbase等都是基于日志结构的存储引擎.最近我在看<数据密集型应用系统设计>,里面有一章专门在讲日志结构的存储引擎的 ...
最新文章
- Nature子刊:微生物来源分析包SourceTracker——结果解读和使用教程
- java 初始化duration_Java 8-Duration 详解
- pl/sql developer导入导出
- rsync 未授权访问漏洞
- 深大计算机科学与技术在广东省,广东考生请注意:深圳大学2021年计划本省总招生人数比例超过75%!...
- I love exam HDU - 6968
- 【POJ - 1330】Nearest Common Ancestors(lca,模板题)
- 简单理解极大似然估计MLE
- sap 打印预览界面点击打印时记录打印次数_9个Excel打印神技巧!从此打印不求人!...
- 关于天猫魔盒tmb100系列 开机灯亮显示器无反应的问题分析
- IBM的量子云计算准备开始商用 我国此前已实现量子加密产品商用
- 苹果7pnfc功能门禁卡_苹果手机怎么刷门禁卡?iPhone刷门禁卡的设置方法
- Linux 3g上网卡 拨号,Linux 系统 CDMA 无线上网卡拨号过程
- 白菜萝卜的做法 - 凉拌菜
- K64 计算 UART波特率
- 2022可用的免费天气预报API接口
- 云南高性能云桌面搭建解决方案、云桌面与传统PC优势对比,云桌面适用场景分析
- MBIST --- PATR1.Memorybist测试原理
- R学习笔记1:导入导出与变量清理
- 基于STM32F103C8T6四路AD采集数据显示在oled屏上非DMA传输方式(附百度网盘下载链接)