C++中的set和map

一、底层

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不支持[]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值