一、底层
set的底层就是key模型的二叉搜索树
map的底层就是key/value模型的二叉搜索树
二、set
2.1序列式容器和关联式容器的概念
序列式容器:如顺序表等顺序存储,数据间彼此交换没有太大影响的容器
关联式容器:如map与set等,数据间的顺序相对固定,不可以随意互换
2.2set的声明
template < class T, // set::key_type/value_type class Compare = less<T>, // set::key_compare/value_compare class Alloc = allocator<T> // set::allocator_type > class set;
T是数据类型,Compare是仿函数,Alloc是空间配置器
默认的仿函数是less,排升序,可以传greater来更改为降序,在模拟实现的时候我们可以直接更改仿函数实现逻辑中类似
less:if(cur->key<key)
greater:if(key<cur->key)
2.3set的使用
2.3.1遍历
①迭代器区间遍历
会默认中序遍历输出,此时输出有序,begin()对应最小值所在的迭代器,end()对应最大值所在迭代器的下一个位置
②范围for遍历
注:set的插入+遍历实现的是去重+排序,他的底层正是二叉搜索树,符合二叉搜索树规范
2.3.2构造
①无参构造
explicit set (const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type());
本质上是两个缺省值,在有需要的时候可以传仿函数和空间配置器来完成构造
②迭代器区间构造
template <class InputIterator> set (InputIterator first, InputIterator last, const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type());
③拷贝构造
set (const set& x);
2.3.3插入
①传入值实现插入
pair<iterator,bool> insert (const value_type& val);
返回值为键值对,我们可以访问second来确定有没有插入成功,也可以访问first来获取插入值的迭代器
此时即使我们插入失败了,依旧可以通过first获得插入值的迭代器,此时我们获得迭代器是原来已有值的位置的迭代器
②位置+传入值插入
iterator insert (iterator position, const value_type& val);
会在position对应位置的下一个位置继续插入新的val,此处以返回值的方式解决迭代器失效问题
③迭代器区间插入
template <class InputIterator> void insert (InputIterator first, InputIterator last);
2.3.4查找
iterator find (const value_type& val) const;
找到会返回迭代器,如果没找到会返回end()
2.3.4补:与算法中find的区别
他们有效率上的区别,算法中时间复杂对为O(N),但是此处的查找为O(logN),因为set被改善为平衡二叉搜索树,小的向左直接走,大的向右直接走,且不会出现极端情况退化为链表
2.3.5count
size_type count (const value_type& val) const;
给一个值val,返回它在当前容器中的个数(multiset里会更有用一些)
不过在set中,count也可以便捷地查找在不在,代替一下find,但他们的效率是一样的
2.3.6lower_bound与upper_bound
iterator lower_bound (const value_type& val) const;
lower_bound会返回大于等于传入值的迭代器
iterator upper_bound (const value_type& val) const;
upper_bound会返回大于传入值的迭代器
可以用来划定一块空间
2.3.7equal_range
pair<iterator,iterator> equal_range (const value_type& val) const;
找与传入值相等的一块区间,在multiset中用处较大
三、multiset
3.1特性
允许值的冗余
3.2声明
template < class T, // multiset::key_type/value_type class Compare = less<T>, // multiset::key_compare/value_compare class Alloc = allocator<T> > // multiset::allocator_type > class multiset;
与set的声明部分是一样的
3.3multiset的使用
3.3.1插入时的变化
在插入时如果遇到相等的情况,放到左/右都可以
当然,,他并不会一直往一边走,而是会在插入玩以后走旋转的逻辑,让树的左右高度几乎相同
与set不同,他不会直接跳过,而是会照常进行插入
iterator insert (const value_type& val);
相应的返回值也不再是键值对
3.3.2查找时的变化
查找的时候如果有多个值val,会返回中序遍历的第一个val的迭代器
这样的好处就是可以通过++配合while循环来找到接下来所有值为val的节点
3.3.3count
size_type count (const value_type& val) const;
此时的count用处就比set中大很多了,可以实现统计值为val的节点个数
3.3.4删除时的变化
在删除时如果有多个相同的val,会删除全部的val,无论出现多少次
既然是改变值,那就有迭代器失效的问题存在
并且声明为
size_type erase (const value_type& val);
我么在实现的过程中可以通过保存一个迭代器进行++之后再删除来应对迭代器失效
3.3.5equal_range
pair<iterator,iterator> equal_range (const value_type& val) const;
会返回值等于val的区间,左闭右开
四、map
4.1map的声明
template < class Key, // map::key_type class T, // map::mapped_type class Compare = less<Key>, // map::key_compare class Alloc = allocator<pair<const Key,T> > // map::allocator_type > class map;
map符合key/value模型,声明中没有缺省值的两个
Key对应key
T对应value
其余的仿函数和空间配置器与set同理
4.2键值对pair
这是一个类模板,采用srtuct来允许外界对其成员变量中的“value”部分进行修改
template <class T1, class T2> struct pair;
它拥有两个成员变量,
T1类型的first与T2类型的second
4.2补:map中pair的特点
在map中,我们对pair进行了typedef
为了保证其中key的位置不可更改
typedef pair<const Key, T> value_type;
4.3map的插入
在map的插入过程中,我们需要同时插入key与value,但要注意并不像这样插入
m.insert("left","左边");//假设m是一个创建好的map对象
而是插入一个pair类型的键值对
insert的声明为
pair<iterator,bool> insert (const value_type& val);
返回的是迭代器+布尔类型的pair
4.3.1 创建一个pair再插入
pair<string , string> kv1("left","左边");
m.insert(kv1);
4.3.2创建匿名对象pair并插入
m.insert(pair<string , string> ("left","左边"));
4.3.3调用函数模板make_pair创建pair ⭐推荐
m.insert(make_pair("left","左边"));
4.3.4多参数构造函数的隐式类型转换 ⭐推荐
m.insert({"left","左边"});
4.3补:
①initializer_list初始化
C++11支持的initializer_list也可以用来构建map
map<string , string> dict = {{"left","左边"},{"right","右边"}};
②插入时是否成功的判断标准
当insert两个key相同的pair时,并不会完成修改,例如
m.insert(make_pair("left","左边"));
m.insert(make_pair("left","左边,剩下的"));
最后的结果只会是插入失败
4.4map的遍历
4.4.1遍历时的打印问题
pair并没有重载<<,在遍历一个map的时候我们需要先打印key,再打印value
pair<string , string>iterator ite = m.begin();
while(ite!=end())
{
cout<<(*ite).first<<":"<<(*ite).second<<endl;
ite++;
}
注:打印的时候会发现打印出来的key是按照字典序排序完成后的,这是因为begin()到end()默认走中序遍历,而二叉搜索树的中序遍历正是有序的(map底层是一棵二叉搜索树)。
在上述代码中,频繁的解引用加成员访问实在有些冗余,我们若是使用迭代器中对于->的重载会让代码简化很多
pair<string , string>iterator ite = m.begin();
while(ite!=end())
{
cout<< ite->first << ":" << ite->second <<endl;
ite++;
}
(重载->本质为 ite.operator->()->first ,但是为了美观省略了一个->)
4.4.2范围for遍历需要保证格式
在使用范围for遍历map类型的m时,如果写成
for(auto e: m){//...}
那么一旦map中存有需要深拷贝的自定义类型,那么每次m的迭代器的(*it)赋值给e的过程都会进行深拷贝,大幅降低效率,因此一定要:
for(const auto& e: m){//...}
4.4补:C++17中结构化绑定的新写法
for(auto& [x,y] : m)
{
cout<<x<<":"<<y<<endl;
}
对应的意思是每个m中有两个值,把其中一个给x,另一个给y
4.5operator[]
4.5.1声明
mapped_type& operator[] (const key_type& k);
返回值是mapped_type&,其实就是k对应value的引用
4.5.2功能解析
它的功能大概可以理解为:
传入key,返回对应value的引用;如果没有key的话,则直接插入这一个key,同时返回其value的引用
具体的功能实现可以转化为
(*((this->insert(make_pair(k,mapped_type()))).first)).second
①make_pair(k,mapped_type())
这段代码本意是创建一个键值对并返回,它的key是k,value为默认值(mapped_type()会调用自定义类型的默认构造,内置类型的默认值)
获取了这样一个pair我们传入给insert
②(this->insert(make_pair(k,mapped_type())))
this指针对应的是当前map,因为insert的声明为
pair<iterator,bool> insert (const value_type& val);
所以其返回值类型是pair<iterator,bool>
此时插入的结果可以分两种情况
1>插入成功,返回新的key对应的迭代器和true组成的pair
newkey_iterator,true
2>插入失败,返回已有的key对应的迭代器和false组成的pair
equalkey_iterator,false
对于这样一个pair<iterator,bool>类型的变量,
它的first对应一个迭代器,解引用这一个迭代器可以得到这一个key在map中对应的pair,这时再访问second就可以得到这一个key在map中对应的pair的value
4.5.3使用举例
[]可以有多种用途,如
map<string,string> dict;//假设要实现一个字典
//不使用[]时候的插入
dict.insert(make_pair("left","左边"));
//使用[]时候的插入
dict["left"] = "左边";
//使用[]的时候不仅可以实现插入,还可以实现修改,如在上一步的基础上
dict["left"] = "左边,剩下的";
//还可以只进行key的插入,value使用默认值
dict["right"];
//在保证key存在的情况下,还可以用来查找key对应的value
cout << dict["left"] << endl;
4.6multimap
①允许key的冗余(map系列都不看value是否重复,不管是map还是mutimap)
②find返回的也是中序第一个符合的
③区别于map,mutimap不支持[]