C++-map和set

本期我们来学习map和set

目录

关联式容器

键值对

pair

树形结构的关联式容器

set

multiset

map

multimap


关联式容器

我们已经接触过 STL 中的部分容器,比如: vector 、 list 、 deque 、forward_list(C++11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。那什么是关联式容器?它与序列式容器有什么区别?
关联式容器 也是用来存储数据的,与序列式容器不同的是,其 里面存储的是 <key, value> 结构的 键值对,在数据检索时比序列式容器效率更高

键值对

用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量 key value key 表键值, value 表示与 key 对应的信息 。比如:现在要建立一个英汉互译的字典,那该字典中必然有英文单词与其对应的中文含义,而且,英文单词与其中文含义是一一对应的关系,即通过该应该单词,在词典中就可以找到与其对应的中文含义。

SGI-STL中关于键值对的定义:

pair

cpp 复制代码
template <class T1, class T2>
struct pair
{
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair(): first(T1()), second(T2())
{}
pair(const T1& a, const T2& b): first(a), second(b)
{}
};

树形结构的关联式容器

根据应用场景的不桶, STL 总共实现了两种不同结构的管理式容器:树型结构与哈希结构。 树型结 构的关联式容器主要有四种: map set multimap multiset 。这四种容器的共同点是:使用平衡搜索树( 即红黑树 ) 作为其底层结果,容器中的元素是一个有序的序列。下面一依次介绍每一个容器。

set

  1. set 是按照一定次序存储元素的容器
  2. 在 set 中,元素的 value 也标识它 (value 就是 key ,类型为 T) ,并且每个 value 必须是唯一的。
    set 中的元素不能在容器中修改 ( 元素总是 const) ,但是可以从容器中插入或删除它们。
  3. 在内部, set 中的元素总是按照其内部比较对象 ( 类型比较 ) 所指示的特定严格弱排序准则进行排序。
  4. set 容器通过 key 访问单个元素的速度通常比 unordered_set 容器慢,但它们允许根据顺序对
    子集进行直接迭代。
  5. set 在底层是用二叉搜索树 ( 红黑树 ) 实现的。
    注意:
  6. 与 map/multimap 不同, map/multimap 中存储的是真正的键值对 <key, value> , set 中只放
    value ,但在底层实际存放的是由 <value, value> 构成的键值对。
  7. set 中插入元素时,只需要插入 value 即可,不需要构造键值对。
  8. set 中的元素不可以重复 ( 因此可以使用 set 进行去重 ) 。
  9. 使用 set 的迭代器遍历 set 中的元素,可以得到有序序列
  10. set 中的元素默认按照小于来比较
  11. set 中查找某个元素,时间复杂度为: log_2 n
  12. set 中的元素不允许修改 ( 为什么 ?)
  13. set 中的底层使用二叉搜索树 ( 红黑树 ) 来实现

我们发现,set的模板参数比我们之前学的多了一个compare,这是仿函数,支持key比较大小的

我们来看它的构造,有拷贝构造,默认构造以及迭代器区间初始化

set的迭代器是双向迭代器

这里的key_type和value_type都是T,这里我们后面会讲

下面我们简单使用一下

使用没什么问题,我们在这里还发现一个问题,它的输出结果是有序的,因为它走的是中序遍历

而且还会去重,去重的原理是如果一个值已经有了,那就不插入

也可以使用范围for遍历

erase支持迭代器位置,值已经迭代器区间

我们根据情况选择即可

如果直接删除值,值不存在没什么问题,但如果用find先查找的话,这里就会报错,因为pos不存在

我们再看看这两个find有什么区别,一个是set自己的find,另一个是算法库里的

set自己的find最多查找高度次,时间复杂度是O(longN),而算法库的find是暴力查找,是O(N),所以我们最好不要用库里面的

还有一个count,这个set这里基本用不到

和find差不多,我们传一个元素,如果在返回1,不在就返回0

find是返回迭代器,而count是返回1或者0

还有这三个函数,是寻找边界的特殊情况用的,我们来看看例子就明白了

比如我们查找30和60,itlow得到的就是30,而itup是比60大的,因为迭代器区间是左闭右开,这样就可以和迭代器适配,可以保证30到60之间删除(包括60)

如果我们找35,得到的是40,lower_bound查找的是>=的值,upper_bound是>

equal_range也是一样,会查找一个左闭右开的区间

multiset

我们再看这个容器,它用起来和set是一样的

它和set的区别就是它允许有重复

如果我们要删除所有的5,我们上面的equal_range就是这样使用的 ,count也是在这里使用,这里可以算出有多少个5,我们可以认为这两个接口就是专门为multiset设计的,set有这两个接口只是为了保持接口一致罢了

当有重复值时,find返回的是中序的第一个

equal_range返回的是>=,如果给的值不存在,返回的就是不存在的区间

map

  1. map 是关联容器,它按照特定的次序 ( 按照 key 来比较 ) 存储由键值 key 和值 value 组合而成的元素。
  2. 在 map 中,键值 key 通常用于排序和惟一地标识元素,而值 value 中存储与此键值 key 关联的
    内容。键值 key 和值 value 的类型可能不同,并且在 map 的内部, key 与 value 通过成员类型
    value_type 绑定在一起,为其取别名称为 pair:
    typedef pair<const key, T> value_type;
  3. 在内部, map 中的元素总是按照键值 key 进行比较排序的。
  4. map 中通过键值访问单个元素的速度通常比 unordered_map 容器慢,但 map 允许根据顺序
    对元素进行直接迭代 ( 即对 map 中的元素进行迭代时,可以得到一个有序的序列 ) 。
  5. map 支持下标访问符,即在 [] 中放入 key ,就可以找到与 key 对应的 value 。
  6. map 通常被实现为二叉搜索树 ( 更准确的说:平衡二叉搜索树 ( 红黑树 )) 。

map这里也有三个type,下面我们简单使用一下

我们先看insert

map是kv模型,所以使用起来非常麻烦,那么可不可以像我们以前那样直接传呢?答案是可以的

原因是有make_pair

就像这样,这里就是map的插入

当然还可以更简洁一点,直接用{ }括起来,这是因为C++11支持多参数的构造函数隐式类型转换,也就是说这种写法在C++98是不能用的

接着我们来遍历,但是按照我们之前写的遍历,这里就出错了,原因是pair不支持流插入和流提取

那为什么这里不像我们写的那样直接用key和value,而要使用一个pair?

原因是operator*返回的话不方便,如果直接使用,C++是不支持返回两个值的,所以设计了一个pair结构

所以得这样写

如果觉得上面的写法麻烦还可以用->

大概就是这样

我们之前也说过,如果不是编译器优化,这里是要有两个箭头的 ,第一个是运算符重载,第二个是访问

也可以用范围for

first是不允许修改的,second可以修改

如果key相同,value不同,是不能插入了,key相同时不插入,不覆盖 ,也就是插入过程中只比较key,value无所谓

erase也是一样,只看key在不在

下面我们来看看[ ]

我们来看这个统计次数,我们以后就可以直接用map

而且代码可以优化,我们可以使用 [ ]

map的[ ] 不是常规的,而是给key,返回对应的value

后面的++我们懂,但是水果第一次出现是怎么回事呢?

它返回了这么个东西,借助了insert的返回值

我们可以看到insert的返回值是一个pair

大致翻译一下:这里返回一个pair,这个pair的first被设置为迭代器,要么指向新插入的元素,要么指向和key相等的元素,second被设计为true,如果key在里面返回false

也就是说,key已经在树里面,返回pair<树里面key所在节点的iterator,false>,如果key不在树里面,返回pair<新插入key所在节点的iterator,true>

如果我们自己设计就是这样,ret里除了key,另一个是匿名对象,根据value的变化而变化,如果key不在树里面, 那就插入成功,如果value是int那就是0,如果是string就是空的string,如果插入失败,也没有影响,因为insert只看key,而return时,first是迭代器,有了迭代器我们就可以取到second

我们结合起来理解一下,水果第一次出现时,insert,key是水果,value是int,int的匿名对象缺省值是0,然后返回这个次数,++一下就变成了1,如果有的话,那就插入失败,再返回次数,++次数就可以计数了

我们来一步一步看这个

我们经过dict["sort"]时是不影响的

这里是可以读的,也就是说,这里是查找+读

经过dict["map"]时,监视窗口多了一个map,value是空的,和我们前面看到的一样,[ ] 的本质是insert

接着我们修改了value(空的value)

这次我们修改了insert的value

最后一个就是插入+修改了(修改空的value)

[ ] 的功能非常多,我们用的时候也就要小心一点

multimap

这个也没啥好说的,对于map就和multiset对于set似的,就是可以有重复的key

不同的地方在于multimap没有提供operator[ ],所以insert也不一样了,不提供pair了,插入永远成功,其他功能一样

以上即为本期全部内容,希望大家可以有所收获

如有错误,还请指正

相关推荐
ChoSeitaku3 分钟前
链表交集相关算法题|AB链表公共元素生成链表C|AB链表交集存放于A|连续子序列|相交链表求交点位置(C)
数据结构·考研·链表
秋の花4 分钟前
【JAVA基础】Java集合基础
java·开发语言·windows
偷心编程5 分钟前
双向链表专题
数据结构
香菜大丸5 分钟前
链表的归并排序
数据结构·算法·链表
jrrz08285 分钟前
LeetCode 热题100(七)【链表】(1)
数据结构·c++·算法·leetcode·链表
小松学前端7 分钟前
第六章 7.0 LinkList
java·开发语言·网络
可峰科技16 分钟前
斗破QT编程入门系列之二:认识Qt:编写一个HelloWorld程序(四星斗师)
开发语言·qt
oliveira-time17 分钟前
golang学习2
算法
咖啡里的茶i20 分钟前
Vehicle友元Date多态Sedan和Truck
c++
全栈开发圈20 分钟前
新书速览|Java网络爬虫精解与实践
java·开发语言·爬虫