《map和set的使用介绍》

引言:

上次我们学习了第一个高阶数据结构---二叉搜索树 ,趁热打铁,今天我们就再来学习两个数据结构---mapset

一:序列式容器和关联式容器

前面我们已经接触过STL 中的部分容器如:stringvectorlistdequearrayforward_list等,这些容器统称为序列式容器 ,因为逻辑结构为线性序列 的数据结构,两个位置存储的值之间一般没有紧密的关联关系,比如交换一下,他依旧是序列式容器。顺序容器中的元素是按他们在容器中的存储位置 来顺序保存和访问的。
关联式容器 也是用来存储数据的,与序列式容器 不同的是,关联式容器 逻辑结构通常是非线性结构 ,两个位置有紧密的关联关系,交换一下,他的存储结构就被破坏了。顺序容器中的元素是按关键字 来保存和访问的。关联式容器map/set系列和unordered_map/unordered_set系列。

本章节我们要学习的mapset底层是红黑树红黑树 是一颗平衡二叉搜索树setkey搜索场景的结构,mapkey/value搜索场景的结构。

二:set系列的使用

1. setmultiset 的参考文档:

set / multiset的参考文档:

2. set类的介绍:

set的声明如下,T就是set底层关键字的类型

  1. set默认要求T⽀持小于比较,如果不支持或者想按自己的需求走可以自行实现仿函数传给第二个模版参数。
    2.·set底层存储数据的内存是从空间配置器申请的,如果需要可以自己实现内存池,传给第三个参数。
  2. 一般情况下,我们都不需要传后两个模版参数。
  3. set底层是用红黑树 实现,增删查效率是O(logN) ,迭代器遍历是走的搜索树的中序,所以是有序的。
  4. 前面部分我们已经学习了vector/list等容器的使用,STL 容器接口设计,高度相似,所以这里我们就不再一个接口一个接口的介绍,只是挑比较重要的接口进行介绍。
    template < class T,
    class Compare = less<T>,
    class Alloc = allocator<T> > class set;

3. set的构造和迭代器

(1)构造函数:

介绍文档:
set的构造我们关注以下几个接口即可。
set支持正向和反向迭代遍历,遍历默认按升序顺序,因为底层是二叉搜索树,迭代器遍历走的中序,支持迭代器就意味着支持范围for,setiteratorconst_iterator都不支持迭代器修改数据,修改关键字数据,破坏了底层搜索树的结构。

代码演示:

(1) 无参默认构造
explicit set (const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());

(2) 迭代器区间构造
template <class InputIterator>
set (InputIterator first, InputIterator last,
const key_compare& comp = key_compare(),
const allocator_type& = allocator_type());

(3) 拷贝构造
set (const set& x);

(5) initializer 列表构造
set (initializer_list<value_type> il,
const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());

(2) 迭代器:

begin():返回指向第一个数据的迭代器。
end():返回最后一个数据下一个位置的迭代器。
cbegin():返回最后一个元素的迭代器。
cend():返回第一个数据的前一个位置的迭代器。
set的迭代器是一个双向迭代器
iterator -> a bidirectional iterator to const value_type

  1. 正向迭代器
    iterator begin();
    iterator end();
  2. 反向迭代器
    reverse_iterator rbegin();
    reverse_iterator rend();

4. set的增删查:

(1)插入:

insert函数

代码演示:
(2)查找:
  1. find(): 查找val,返回val所在的迭代器,没有找到返回end()。
  2. count():查找val,返回Val的个数。
代码演示:
(3)删除:
  1. iterator erase (const_iterator position): 删除一个迭代器位置的值。
  2. size_type erase (const value_type& val):删除val,不存在返回0,存在返回1。
  3. iterator erase (const_iterator first, const_iterator last):删除一段迭代器区间的值。
代码演示:



(4) lower_boundupper_bound
  1. lower_bound:返回大于等val位置的迭代器。
  2. upper_bound:返回大于val位置的迭代器。

注:因此借助这两个接口就可以得到一段左闭右开的区间。

代码演示:

5. insert 和迭代器遍历使用样例:


注:这里的范围for我们写成了引用的形式,是为了提高效率。

6. find和erase的使用样例:



注:这是我们借助count间接实现的查找。

算法库中的findset中的find对比:

7. multisetset的差异

multisetset的使用基本完全类似,主要区别点在于multiset支持值冗余,那么
insert/find/count/erase都围绕着支持值冗余有所差异,具体参看下面的样例代码理解:


8. 牛刀小试:

(1)题目描述:
代码解决:
方法一-暴力匹配:

先将两个数组通过set来去重,之后遍历其中一个来借助count来判断是否有交集。

方法二-双指针:

该解法的思路是在去重+升序排序 后的基础上来遍历两个容器

定义两个指针p1p2分别遍历两个set

  1. 小的那个++
  2. 相等的话就存下来,p1++p2++
  3. 当其中一个容器遍历完之后,就结束、
    为什么这样是对的呢?
    因为如果其中一个小的话,由于数据是升序的,因此当前必不可能为交集,若存在交集的话只可能在它之后,因此往后走。
题目传送门:349. 两个数组的交集

三: map系列的使用

1. mapmultimap的介绍文档:

map 和multimap的介绍文档:

2. map类的介绍:

map的声明如下,Key就是map底层关键字的类型,Tmap底层value的类型,map默认要求Key支持小于比较,如果不支持或者需要的话可以自行实现仿函数传给第二个模版参数,map底层存储数据的内存是从空间配置器申请的。一般情况下,我们都不需要传后两个模版参数。map底层是用红黑树 实现,增删查改效率是,迭代器遍历是走的中序,所以是按key有序顺序遍历的。O(logN) ,迭代器遍历是走的中序,所以是按key有序顺序遍历的。

template < class Key,
class T,
class Compare = less<Key>,
class Alloc = allocator<pair<const Key,T> > > class map;

3. pair类型介绍:

map底层的红黑树 节点中的数据,使用pair<Key,T>存储键值对 数据。
pair:
typedef pair<const Key, T> value_type;
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)
{}
template<class U, class V>
pair (const pair<U,V>& pr): first(pr.first), second(pr.second)
{}
};
template <class T1,class T2>
inline pair<T1,T2> make_pair (T1 x, T2 y)
{
return ( pair<T1,T2>(x,y) );
}

上面是pair的底层结构,实质上就是一个类模版的结构体。先这么理解即可。

思考:为什么map里面要用pair呢?

这是因为map里面是存储两个数据,如果你单独分开存两个数据的话,是没办法解引用的,因为解引用只能拿到一个数据,用pair的话,解引用会拿到pair类型的数据,之后可以通过pair类型的性质来拿到两个数据。

4. map的构造和迭代器:

(1) 构造函数:

map的构造函数介绍文档:
map的构造我们关注以下几个接口即可。
map的支持正向和反向迭代遍历,遍历默认按key的升序顺序,因为底层是二叉搜索树,迭代器遍历走的中序;支持迭代器就意味着支持范围for,map支持修改value数据,不支持修改key数据,修改关键字数据,破坏了底层搜索树的结构。

(1) 无参默认构造
explicit map (const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());

(2) 迭代器区间构造
template <class InputIterator>
map (InputIterator first, InputIterator last,
const key_compare& comp = key_compare(),
const allocator_type& = allocator_type());

(3) 拷贝构造
map (const map& x);

(4) initializer 列表构造
map (initializer_list<value_type> il,

(2)迭代器:
  1. begin():返回指向第一个数据的迭代器。
  2. end():返回最后一个数据下一个位置的迭代器。
  3. cbegin():返回最后一个元素的迭代器。
  4. cend():返回第一个数据的前一个位置的迭代器。

map的迭代器也是⼀个双向迭代器
iterator -> a bidirectional iterator to const value_type

  1. 正向迭代器
    iterator begin();
    iterator end();
  2. 反向迭代器
    reverse_iterator rbegin();
    reverse_iterator rend();

5. map 的增删查

(1)插入:

insert介绍文档:

代码演示:
(2)查找:
  1. find():查找k,返回k所在的迭代器,没有找到返回end()。
  2. count():查找k,返回k的个数。
代码演示:
(3) 删除:
  1. iterator erase (const_iterator position):删除⼀个迭代器位置的值。
  2. size_type erase (const key_type& k):删除k,k存在返回0,存在返回1。
  3. iterator erase (const_iterator first, const_iterator last:删除一段迭代器区间的值。
(4) lower_boundupper_bound
  1. lower_bound:返回大于等k位置的迭代器。
  2. upper_bound:返回大于k位置的迭代器。
代码演示:


注:这里默认是按照第一个关键字来比较的。

6. map的数据修改

前面提到map支持修改mapped_type数据,不支持持修改key数据,修改关键字数据,破坏了底层搜索树的结构。
map第一个支持修改的方式是通过迭代器,迭代器遍历时或者find返回key所在的iterator修改,map还有一个非常重要的修改接口operator[],但是operator[]不仅仅支持修改,还支持插入数据和查找数据,所以他是一个多功能复合接口

需要注意从内部实现角度,map这里把我们传统说的value值,给的是T类型
typedefmapped_type。而value_type是红黑树结点中存储的pair键值对值。日常使用我们还是习惯将这里的T映射值叫做value

7.map的迭代器和[]功能样例:

其实这里的代码还可以这样写:

这样写的话,对于已经存在的数据的修改大家都没问题,但是一些同学可能会疑惑数据第一次出现的时候是怎么统计的呢?

其实在数据第一次出现的时候,会先将这个数据插入,这时候第二个参数就是一个默认值0,之后在进行修改。

operator[] 底层解释:

要搞清楚operator[]的底层就需要先从insert入手。

可以看到insert的第一个实现形式的返回值是pair类型,由迭代器和bool值组成。

我们再来看第一种形式的返回值的解读:

这里说的是当该数据是第一次插入的话,就会返回指向这个新插入数据的迭代器,否则就会返回map中指向该数据的迭代器,第二个bool值,如果数据是第一次插入的新数据就会返回true,否则就是返回false

因此operator[]其实大概就是这样实现的:

V& operator[](const K& key)
{
pair<iteraotr,bool> ret = insert(key);
return ret.first->second;
}

8. multimapmap的差异

multimapmap的使用基本完全类似,主要区别点在于multimap支持关键值key冗余,那么insert/find/count/erase都围绕着支持关键值key冗余有所差异,这里跟setmultiset完全⼀样,比如find时,有多个key,返回中序第一个。其次就是multimap不支持[],因为支持key冗余,[]就只能支持插入了,不能支持修改。

9. 牛刀小试:

(1)题目描述:
(2)思路分析:

首先从题目就能知道这是一个经典的TopK 问题,但是与之前不同的是这个是双元素的,因此我们就得用pair来存储,所以我们的思路就是先用map来统计各单词的出现次数,再创建小根堆 来维护前k个出现次数最多的单词,之后用vector来存储结果,但由于我们创建的是小根堆,因此在返回结果的时候还需要进行逆置。

(3)代码实现:
(4)题目传送门:

692. 前K个高频单词

相关推荐
FlashWarrior30 分钟前
RocksDB rate limiter讲解
c++·数据库开发·数据库架构
老土豆FUSK43 分钟前
C++ 函数模板
c++
红狐寻道3 小时前
osgEarth初探
c++·后端
Dark__Monarch4 小时前
我的世界之战争星球 暮色苍茫篇 第二十二章、夜影
c++
爱奥尼欧5 小时前
【C++语法】类和对象(4)——日期类和const成员函数
数据库·c++
岁忧5 小时前
(LeetCode 面试经典 150 题) 169. 多数元素(哈希表 || 二分查找)
java·c++·算法·leetcode·go·散列表
fpcc6 小时前
c++26新功能—hive容器
c++·hive
让我们一起加油好吗6 小时前
【基础算法】二分(二分查找 + 二分答案)
c++·算法·leetcode·二分·洛谷
范纹杉想快点毕业7 小时前
Qt、C++自定义按钮、组件、事件编程开发练习,万字实战解析!!
java·c语言·开发语言·c++·git·qt·github