C++从入门到起飞之——(multi)set与(multi)map的的使用 全方位剖析!

C++从入门到起飞之——(multi)set与(multi)map的的使用 全方位剖析!

🌈个人主页:秋风起,再归来~
🔥系列专栏:C++从入门到起飞          
🔖克心守己,律己则安

目录

1. 序列式容器和关联式容器

2. set系列的使⽤

2.1 set和multiset参考⽂档

2.2 set类的介绍

2.3 set的构造和迭代器

 2.4 set的增删查

2.5 multiset和set的差异 

3、map系列的使用

3.1 map和multimap参考⽂档

3.2 map类的介绍

3.3 pair类型介绍

3.4 map的增删查

3.5 map的数据修改

3.6 multimap和map的差异

4、完结散花


1. 序列式容器和关联式容器

前⾯我们已经接触过STL中的部分容器如:string、vector、list、deque、array、forward_list等,这 些容器统称为序列式容器,因为逻辑结构为线性序列的数据结构,两个位置存储的值之间⼀般没有紧 密的关联关系,⽐如交换⼀下,他依旧是序列式容器。顺序容器中的元素是按他们在容器中的存储位 置来顺序保存和访问的。

关联式容器也是⽤来存储数据的,与序列式容器不同的是,关联式容器逻辑结构通常是⾮线性结构, 两个位置有紧密的关联关系,交换⼀下,他的存储结构就被破坏了。顺序容器中的元素是按关键字来 保存和访问的。关联式容器有map/set系列和unordered_map/unordered_set系列。

本文讲解的map和set底层是红⿊树,红⿊树是⼀颗平衡⼆叉搜索树。set是key搜索场景的结构, map是key/value搜索场景的结构。

2. set系列的使⽤

2.1 set和multiset参考⽂档

https://legacy.cplusplus.com/

2.2 set类的介绍

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;

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

• set默认要求T⽀持⼩于⽐较,如果不⽀持或者想按⾃⼰的需求⾛可以⾃⾏实现仿函数传给第⼆个模 版参数

• set底层存储数据的内存是从空间配置器申请的,如果需要可以⾃⼰实现内存池,传给第三个参 数。

• ⼀般情况下,我们都不需要传后两个模版参数。

• set底层是⽤红⿊树实现,增删查效率是O(logN),迭代器遍历是⾛的搜索树的中序,所以是有序 的。 

• 前⾯部分我们已经学习了vector/list等容器的使⽤,STL容器接⼝设计,⾼度相似,所以这⾥我们 就不再⼀个接⼝⼀个接⼝的介绍,⽽是直接带着⼤家看⽂档,挑⽐较重要的接⼝进⾏介绍。

2.3 set的构造和迭代器

set的构造我们关注以下⼏个接⼝即可。

set的⽀持正向和反向迭代遍历,遍历默认按升序顺序,因为底层是⼆叉搜索树,迭代器遍历⾛的中 序;⽀持迭代器就意味着⽀持范围for,set的iterator和const_iterator都不⽀持迭代器修改数据,修改 关键字数据,破坏了底层搜索树的结构。

(1)无参数默认构造

explicit set (const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type()); explicit set (const allocator_type& alloc); 

上面的构造函数被声明为explicit,这可阻止它们被用来执行隐式类型转化,但它们仍可被用来执行显示类型转换。 

被声明为explicit的构造函数通常比其兄弟non-explicit更受欢迎,因为它们禁止编译器执行非预期(往往也不被期望)的类型转换。除非我有一个好理由允许构造函数被用于隐式类型转换,否则我会把它声明为explicit。

(2) 迭代器区间构造

set (InputIterator first, InputIterator last, const key_compare& comp = key_compare(), const allocator_type& = allocator_type());

(3) 拷⻉构造

void insert (initializer_list il);
set (const set& x); set (const set& x, const allocator_type& alloc);

 (4) 列表构造

set (initializer_list<value_type> il, const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type());

 2.4 set的增删查

set的增删查关注以下⼏个接⼝即可:

(1)单个数据插⼊,如果已经存在则插⼊失败

pair<iterator,bool> insert (const value_type& val);

pair其实是std库里面的一个类模版

 

set<int> s1; auto it = s1.insert(1); cout << *it.first << endl; cout << it.second<< endl; cout <<typeid(it).name()<< endl;

下面我们还会再提到pair类型 

(2)列表插⼊,已经在容器中存在的值不会插⼊

void insert (initializer_list<value_type> il);

(3)迭代器区间插⼊,已经在容器中存在的值不会插⼊

template <class InputIterator> void insert (InputIterator first, InputIterator last);

(4)查找val,返回val所在的迭代器,没有找到返回end()

iterator find (const value_type& val);

(5)查找val,返回Val的个数

size_type count (const value_type& val) const;

(6)删除⼀个迭代器位置的值

iterator erase (const_iterator position);

(7)删除val,val不存在返回0,存在返回1

size_type erase (const value_type& val);

(8)删除⼀段迭代器区间的值

iterator erase (const_iterator first, const_iterator last);

(9)返回⼤于等val位置的迭代器

iterator lower_bound (const value_type& val) const;

(10)返回⼤于val位置的迭代器

iterator upper_bound (const value_type& val) const;

2.5 multiset和set的差异 

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

// 相⽐set不同的是,multiset是排序,但是不去重 multiset<int> s = { 4,2,7,2,4,8,4,5,4,9 }; auto it = s.begin(); while (it != s.end()) { cout << *it << " "; ++it; } cout << endl; // 相⽐set不同的是,x可能会存在多个,find查找中序的第⼀个 int x; cin >> x; auto pos = s.find(x); while (pos != s.end() && *pos == x) { cout << *pos << " "; ++pos; } cout << endl; // 相⽐set不同的是,count会返回x的实际个数 cout << s.count(x) << endl; // 相⽐set不同的是,erase给值时会删除所有的x s.erase(x); for (auto e : s) { cout << e << " "; } cout << endl;

3、map系列的使用

3.1 map和multimap参考⽂档

https://legacy.cplusplus.com/reference/map/map/?kw=map

3.2 map类的介绍

map的声明如下,Key就是map底层关键字的类型,T是map底层value的类型,set默认要求Key⽀持⼩于⽐较,如果不⽀持或者需要的话可以⾃⾏实现仿函数传给第⼆个模版参数,map底层存储数据的 内存是从空间配置器申请的。⼀般情况下,我们都不需要传后两个模版参数。map底层是⽤红⿊树实 现,增删查改效率是O(logN) ,迭代器遍历是⾛的中序,所以是按key有序顺序遍历的。

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; 

3.3 pair类型介绍

map底层的红⿊树节点中的数据,使⽤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) {} }; 

3.4 map的增删查

map的增删查关注以下⼏个接⼝即可:

 map增接⼝,插⼊的pair键值对数据,跟set所有不同,但是查和删的接⼝只⽤关键字key跟set是完全类似的,不过find返回iterator,不仅仅可以确认key在不在,还找到key映射的value,同时通过迭代 还可以修改value

Member types key_type -> The first template parameter (Key) mapped_type -> The second template parameter (T) value_type -> pair<const key_type,mapped_type> // 单个数据插⼊,如果已经key存在则插⼊失败,key存在相等value不相等也会插⼊失败 pair<iterator,bool> insert (const value_type& val); // 列表插⼊,已经在容器中存在的值不会插⼊ void insert (initializer_list<value_type> il); // 迭代器区间插⼊,已经在容器中存在的值不会插⼊ template <class InputIterator> void insert (InputIterator first, InputIterator last); // 查找k,返回k所在的迭代器,没有找到返回end() iterator find (const key_type& k); // 查找k,返回k的个数 size_type count (const key_type& k) const; // 删除⼀个迭代器位置的值 iterator erase (const_iterator position); // 删除k,k存在返回0,存在返回1 size_type erase (const key_type& k); // 删除⼀段迭代器区间的值 iterator erase (const_iterator first, const_iterator last); // 返回⼤于等k位置的迭代器 iterator lower_bound (const key_type& k); // 返回⼤于k位置的迭代器 const_iterator lower_bound (const key_type& k) const;

我们要注意到map是要插入一个pair类型的,我们可以用匿名对象的方式插入:

map<string , string> m; m.insert(pair<string, string>("left", "左边"));

 不过这种方法太麻烦了,所以我们通常用make_pair的方式:

maek_pair是定义在std上的一个内联函数模版:

template <class T1,class T2> inline pair<T1,T2> make_pair (T1 x, T2 y) { return ( pair<T1,T2>(x,y) ); } 
map<string, string> m; m.insert(make_pair("left", "左边"));

3.5 map的数据修改

前⾯我提到map⽀持修改mapped_type数据,不⽀持修改key数据,修改关键字数据,破坏了底层搜 索树的结构。

map第⼀个⽀持修改的⽅式时通过迭代器,迭代器遍历时或者find返回key所在的iterator修改,map 还有⼀个⾮常重要的修改接operator[],但是operator[]不仅仅⽀持修改,还⽀持插⼊数据和查找数据,所以他是⼀个多功能复合接

需要注意从内部实现⻆度,map这⾥把我们传统说的value值,给的是T类型,typedef为 mapped_type。⽽value_type是红⿊树结点中存储的pair键值对值。⽇常使⽤我们还是习惯将这⾥的 T映射值叫做value。

Member types key_type -> The first template parameter (Key) mapped_type -> The second template parameter (T) value_type -> pair<const key_type,mapped_type> // 查找k,返回k所在的迭代器,没有找到返回end(),如果找到了通过iterator可以修改key对应的 mapped_type值 iterator find (const key_type& k); // ⽂档中对insert返回值的说明 // The single element versions (1) return a pair, with its member pair::first set to an iterator pointing to either the newly inserted element or to the element with an equivalent key in the map. The pair::second element in the pair is set to true if a new element was inserted or false if an equivalent key already existed. // insert插⼊⼀个pair<key, T>对象 // 1、如果key已经在map中,插⼊失败,则返回⼀个pair<iterator,bool>对象,返回pair对象 first是key所在结点的迭代器,second是false // 2、如果key不在在map中,插⼊成功,则返回⼀个pair<iterator,bool>对象,返回pair对象 first是新插⼊key所在结点的迭代器,second是true // 也就是说⽆论插⼊成功还是失败,返回pair<iterator,bool>对象的first都会指向key所在的迭 代器 // 那么也就意味着insert插⼊失败时充当了查找的功能,正是因为这⼀点,insert可以⽤来实现 operator[] // 需要注意的是这⾥有两个pair,不要混淆了,⼀个是map底层红⿊树节点中存的pair<key, T>,另 ⼀个是insert返回值pair<iterator,bool> pair<iterator,bool> insert (const value_type& val); mapped_type& operator[] (const key_type& k); // operator的内部实现 mapped_type& operator[] (const key_type& k) { // 1、如果k不在map中,insert会插⼊k和mapped_type默认值,同时[]返回结点中存储 mapped_type值的引⽤,那么我们可以通过引⽤修改返映射值。所以[]具备了插⼊+修改功能 // 2、如果k在map中,insert会插⼊失败,但是insert返回pair对象的first是指向key结点的 迭代器,返回值同时[]返回结点中存储mapped_type值的引⽤,所以[]具备了查找+修改的功能 pair<iterator, bool> ret = insert({ k, mapped_type() }); iterator it = ret.first; return it->second; } 

3.6 multimap和map的差异

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

4、完结散花

好了,这期的分享到这里就结束了~

如果这篇博客对你有帮助的话,可以用你们的小手指点一个免费的赞并收藏起来哟~

如果期待博主下期内容的话,可以点点关注,避免找不到我了呢~

我们下期不见不散~~

​​

​​

Read more

C++:二叉搜索树

C++:二叉搜索树

Hello大家好! 很高兴与大家见面! 给生活添点快乐,开始今天的编程之路。 我的博客:<但愿. 我的专栏:C语言、题目精讲、算法与数据结构、C++ 欢迎点赞,关注 目录   一  ⼆叉搜索树的性能分析和概念       1.1⼆叉搜索树的概念       1.2⼆叉搜索树的性能分析  二   ⼆叉搜索树增删查的实现,这里不支持修改(修改一个就可能不是二叉搜索树的结构了)       2.1⼆叉搜索树基本结构的实现                 2.1.1节点的定义(由于后面实现⼆叉搜索树要访问节点成员所以这里使用struct定义(默认是公有))                 2.1.2默认成员函数                         2.1.2.1⼆叉搜索树的基本结构                         2.1.2.2⼆叉搜索树的默认构造                         2.

By Ne0inhk

终极指南:awesome-web-components组件库完全评测 - 2026年最新资源大全

终极指南:awesome-web-components组件库完全评测 - 2026年最新资源大全 【免费下载链接】awesome-web-componentsA curated list of awesome Web Components resources. 项目地址: https://gitcode.com/gh_mirrors/aw/awesome-web-components Web Components 是现代前端开发的革命性技术,它让开发者能够创建可复用、封装性强的自定义HTML元素。awesome-web-components 项目是一个精心整理的资源宝库,收录了Web Components生态系统中最重要的工具、库、框架和最佳实践。无论你是初学者还是经验丰富的开发者,这个项目都能为你提供构建现代化Web应用所需的一切资源。 📊 为什么Web Components如此重要? Web Components 是一套浏览器原生支持的Web平台API,它允许开发者创建可重用的自定义元素。这个技术栈由四个核心规范组成: 1. Custom Elements(自定义元

By Ne0inhk
【C++笔记】STL知识铺垫

【C++笔记】STL知识铺垫

前言:          在前面的学习中,我们已经掌握了C++的基础语法和编程概念,本文将深入探讨C++标准库的使用,并详细介绍迭代器、auto关键字以及范围for循环等相关知识。          一、STL简介          1.1 什么是STL          STL(Standard Template Library,标准模板库)是C++标准库的核心组成部分,它不仅提供了可复用的组件库,更是一个集成了高效数据结构与算法的软件框架。          1.2 STL的六大组件          由于历史原因,string 类型先于 STL 出现,STL 后来由惠普实验室开发并开源,因此人们通常不将 string 归入 STL 范畴。                   二、迭代器                  迭代器(Iterator)是 C++ STL 中最精妙的设计之一,如果把 STL 的容器比作各种不同类型的仓库(数组、链表、

By Ne0inhk
《C++进阶之STL》【哈希表】

《C++进阶之STL》【哈希表】

【哈希表】目录 * 前言 * ------------概念介绍------------ * 1. 什么是哈希? * ------------核心术语------------ * 一、哈希函数 * 1. 哈希函数的核心特点是什么? * 2. 哈希函数的设计目标是什么? * 3. 常见的哈希函数有哪些? * 直接定址法 * 除法散列法 * 乘法散列法 * 全域散列法 * 二、负载因子 * 1. 什么是负载因子? * 2. 负载因子对哈希表的性能有什么影响? * 3. 负载因子超过阈值时会发什么? * 三、哈希冲突 * 四、冲突处理 * 方法一:开放定址法 * 线性探测 * 二次探测 * 双重散列 * 方法二:链地址法 * ------------基本操作------------ * 怎么解决键key不能取模的问题? * 一、开放定址法 * 哈希结构 * 删除操作 * 扩容操作 * 二、链地址法 * 哈希结构 *

By Ne0inhk