文章目录
- 1. 容器
- 2. set和multiset
- 2.1 set
- 2.1.1 构造函数
- 2.1.2 insert和erase
- 2.1.2.1 insert
- 2.1.2.2 erase
- 2.1.3 查找和访问
- 2.1.3.1 set迭代器相关
- 2.1.3.2 find && count
- 2.1.3.3 范围查找
- 2.2 multiset
- 2.2.1 insert和erase
- 2.2.2 find和count
- 2.3 set和multiset的在算法题中的应用
- 3. map和multimap
- 3.1 pair介绍
- 3.2 map
- 3.2.1 构造函数
- 3.2.2 insert和erase
- 3.2.3 find和count
- 3.2.4 operator[] 和 at
- 3.3 multimap
1. 容器
在C++的stl中,容器分为序列式容器和关联式容器。
对于序列式容器而言,容器的逻辑结构是线性的,相邻两个位置的数据之间,没有较为紧密的联系,因此通常是可以交换的。对于这类容器的数据访问,一般都是通过数据存储的位置进行访问,最典型的例子,如vector
对于关联式容器,容器的逻辑结构是非线性的,相邻两个位置的数据之间,是有紧密的联系,通常是不可以交换的,一旦交换,便会破坏整个容器的结构。像map和set,就是关联式容器的代表,这类容器中数据的访问,一般都是借助于key,即键值来进行访问的。
2. set和multiset
2.1 set
set的底层是不支持键值冗余的二叉搜索树,存储的是key,而非key-value。
以下,我们介绍使用set容器时,几个非常重要的接口。
2.1.1 构造函数
set的构造函数,有三个是比较重要的。
一个是默认构造。
默认构造中,关于内存池的形参一般不用在意,除非有别的内存池可以供你调用。
set中的仿函数需要注意,默认的仿函数进行的是小于的比较,因此,默认对整个set容器的遍历,是依据键值得到的升序。如果想要得到降序,可以传入一个进行大于比较的仿函数。
一个是使用迭代器进行构造。
还有一个是在C++11中引入的,使用初始化列表这种容器进行构造。
2.1.2 insert和erase
2.1.2.1 insert
在set中,我们使用insert
来进行键值的插入。
较为常用的主要是三个:直接插入键值的,插入迭代器区间的,插入初始化列表的。
直接插入键值的:
我们主要看第一个左值引用,第二个右值引用暂不做讨论。
这个函数重载就是直接插入键值,比较特殊的是它的返回值,返回的是一个pair类型。
在这个pair类型中,有两个成员,第一个成员是set的迭代器,第二个成员是bool型的变量。
由于set是不允许键值冗余的二叉搜索树,因此set是存在插入失败的情况。在插入成功时,pair中包含插入位置对应的迭代器,以及一个true;插入失败时,返回二叉搜索树中某个已存在key值的迭代器(此key值与所要插入的key值相同),以及一个false。
其余的两种形式:
2.1.2.2 erase
erase有三种删除形式,其中前两种使用得最多。
iterator erase(const_iterator position):这是利用迭代器进行删除,删除的是迭代器所对应的key,返回值也是一个迭代器——一般是返回删除元素的后一个迭代器,但如果删除的元素后面没有元素,那就返回set.end()
size_type erase (const value_type& val): 这是利用键值去删除。在删除前,先要找到键值对应的具体位置,然后再删除。这个返回值很特殊,是删除的数据个数。这让人有点疑惑,set既然是不支持冗余的,那么删除的数据个数最多只是1,设计这样的返回值感觉没什么用——实际上,这是在设计上与multiset进行一个统一,对于multiset,这个返回值是有意义的。
iterator erase(const_iterator first,const_iterator last):删除一段迭代器区间。
2.1.3 查找和访问
2.1.3.1 set迭代器相关
set的迭代器使用和其它容器一样,这里不做赘述。
需要注意的是,虽然set中也区分了const迭代器和非const迭代器,但是无论是哪种迭代器,都是不能对set中的键值进行修改,因为一旦修改,就很可能破坏整棵二叉搜索树的结构。
2.1.3.2 find && count
利用键值进行查找,返回相应位置的迭代器,对应的有const迭代器和非const迭代器两个版本。
count是去查找set中相应键值的元素个数,这个在set中不是0,就是1,最主要还是在multiset中的使用,不过在设计时,考虑到set和multiset的协同,因此set中也有这个函数。
2.1.3.3 范围查找
范围查找lower_bound和upper_bound配套使用。
对于lower_bound,返回的是整棵二叉搜索树中大于或等于传入val的第一个数据的迭代器;对于upper_bound,返回的是整棵二叉搜索树中大于传入val的第一个数据的迭代器(这里之所以是大于val,缘于迭代器的使用规则,因为迭代器遍历的循环条件总是!= 某个迭代器)。如果这样的数据不存在,那么这两个函数都是返回set.end()。
除了上述的用于查找某个范围的两个函数,还有一个用于查找相同键值范围的函数,这个函数在set中没有啥意义,在multiset中才比较有用。
这个equal_range函数返回的是一个pair类型,存储对应某个范围的左右两个边界的迭代器,注意,右迭代器对应这个范围之后的第一个元素(或是set.end())。如果key值不存在,则pair中的两个成员变量均为set.end()。
2.2 multiset
相较于set,multiset是支持键值冗余的搜素二叉树。
multiset整体的成员函数于set相类似,不过由于其支持键值冗余的特性,在部分函数上会有所差别。
2.2.1 insert和erase
对于insert,由于multiset允许键值冗余,所以不存在插入失败的情况,因此直接返回插入位置对应的迭代器即可。
对于erase,如果使用的是迭代器版本,那么就是将相应位置的元素清除;如果使用传val值的版本,则会将multiset中,所有等于该键值的数据全部删除,并返回删除的数据个数。
2.2.2 find和count
multiset中,存在键值冗余的情况,那么这个find,究竟是查找哪一个数据呢?
find函数默认查找的是整棵二叉搜索树的中序遍历中的键值与val相等的第一个数。
count函数即返回整棵二叉搜索树中,与val相等的键值的个数。
2.3 set和multiset的在算法题中的应用
set是非键值冗余的二叉搜索树,而二叉搜索数的中序遍历又是有序的,所以,我们如果要对一系列数据进行去重+排序处理,那么就可以将这些数据放至set容器中,否则就需要使用算法库中的sort+unique。
multiset是键值冗余的二叉搜索树,如果仅需要实现排序功能,而不需去重,可将数据放至multiset中。
3. map和multimap
map和multimap,底层都是key-value的二叉搜索树,map允许键值冗余,multimap不允许键值冗余。由于key-value需要存储两个数,所以map和multimap底层存放的数据类型是pair,默认pair类型中的第一个成员是key,第二个成员是value。
3.1 pair介绍
pair是用于存储成对的,并且往往有关联的两个数据,分别存储到pair的first成员和second成员中,这两个成员是允许类外直接访问的。
pair中,最重要的便是如何去构造出一个pair对象,以下介绍几种常用的方法。
pair类型的对象也可以进行大小的比较,重载了相关的运算符。
pair类型对象的比较,先比较成员first,再比较成员second。
- 相等比较:两个成员都相等,则相等。
- 大于比较:谁first大,谁就大;first一样大,再比较second,谁second大,谁就大。
- 小于比较:与大于比较逻辑相同,比大转为比小即可。
- 不等于比较:两个成员有一个不相等,便不相等。
3.2 map
3.2.1 构造函数
map的常用构造与set类似,默认构造、拷贝构造和使用一段迭代器区间进行构造。
需要注意的是,由于map的底层数据是pair类型的,因此使用一段迭代器区间进行构造时,迭代器指向的一定要是pair类的对象。
3.2.2 insert和erase
map的insert需要插入一个pair对象。
最常见的插入还是单元素的插入,这种插入通常习惯以下两种写法:
map的erase:
size_type erase(const key_type& k) : map中的erase是按照key值来删除,而非value。
3.2.3 find和count
均是按照键值key来进行查找和统计
3.2.4 operator[] 和 at
方括号在map中的重载,是map中非常重要的一个成员函数,功能也较为复杂。
operator[ ]的重载,需要传一个key值(key_type)进去,返回的是key值相应的value值的引用(mapped_type)。
但实际上,这个重载函数远没有看上去那么简单。这个函数的使用,要分为两种情况:
- 相应的key值存在。此时返回key值相应的val值的引用。
- 相应的key值不存在。此时并不会查找失败,这个函数会调用insert向map中再插入一个pair对象,这个pair对象的key值是传入的key值,value值是利用map中的value类型构造出的匿名对象,然后函数再返回这个value值。
实质上,在相应的key值存在时,函数也会调用insert,只不过会插入失败,不过由于即便插入失败,insert也会返回相应key值对应的迭代器(返回是pair类型,迭代器为其第一个成员),因此可以正常返回相应value值的引用。
以下是stl库中,map容器中operator[ ]函数的实现:
mapped_type& operator[] (const key_type& k)
{pair<iterator, bool> ret = insert({ k, mapped_type() });iterator it = ret.first;return it->second;
}
at与operator[ ]类似,传key值,返回value值,只不过at只能用于已存在的key值查找,不能用于插入不存在的key值,当输入一个不存在的key值时,at函数会抛异常。
以下举一个使用operator[ ]进行次数统计的典型例子:
在main函数中调用,相应的输出为:
3.3 multimap
与map相比较,multimap 是支持键值冗余的key-value二叉搜索树。
multimap与map的差别,同multiset与set的差异相类似,此处不再赘述。
有一点需要注意,map是不支持键值冗余,但是value值是可以相同的,只要相应的key值不同即可;而multimap对于key和value则都是没有限制的。
另外,在multimap中没有了operator[ ]和at这两个成员函数,最主要的原因还是在于键值冗余,因此会无法解决到底返回哪个value值的问题。