当前位置: 首页 > news >正文

第十三讲 | map和set的使用

map和set的使用

  • 一、序列式容器和关联式容器
  • 二、set系列的使用
    • 1、set和multiset参考文档
    • 2、set类的介绍
    • 3、set的构造和迭代器
    • 4、set的增删查
    • 5、insert和迭代器遍历使用样例
    • 6、find和erase使用样例
    • 7、multiset和set的差异
    • 8、算法题
  • 三、map系列的使用
    • 1、map和multimap参考文档
    • 2、map类的介绍
    • 3、pair类型介绍
    • 4、map的构造
    • 5、map的增删查
    • 6、map的数据修改
    • 7、构造遍历及增删查使用样例
    • 8、map的迭代器和[]功能样例
    • 9、multimap和map的差异
    • 10、算法题

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

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

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

本章节讲解的map和set底层是红黑树,红黑树是一颗平衡二叉搜索树。set是key搜索场景的结构,map是key/value搜索场景的结构。

二、set系列的使用

1、set和multiset参考文档

< set >

2、set类的介绍

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

  • set默认要求T支持小于比较,如果不支持或者想按自己的需求走可以自行实现仿函数传给第二个模版参数。

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

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

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

  • 前面部分我们已经学习了vector/list等容器的使用,STL容器接口设计,高度相似,所以这里我们就不再一个接口一个接口的介绍,而是直接带着大家看文档,挑比较重要的接口进行介绍。

在这里插入图片描述

3、set的构造和迭代器

set的构造我们关注以下几个接口即可。

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

在这里插入图片描述

4、set的增删查

set的增删查关注以下几个接口即可:

key_type、value_type因为底层typedef的原因,都表示第一个模板参数T。

在这里插入图片描述

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

set严格来说是"去重+排序",所以使用set来进行去重的操作是非常方便的。

#include<iostream>
#include<set>
using namespace std;
int main()
{// 去重+升序排序set<int> s;// 去重+降序排序(给一个大于的仿函数)//set<int, greater<int>> s;s.insert(5);s.insert(2);s.insert(7);s.insert(5);// auto it = s.begin();set<int>::iterator it = s.begin();while (it != s.end()){// error,set不支持修改//*it = 1;cout << *it << " ";++it;}cout << endl;// 支持迭代器就支持范围for//for (auto e : s)//{//	cout << e << " ";//}// 插⼊⼀段initializer_list列表值,已经存在的值插⼊失败s.insert({ 2, 3, 6, 8 });for (auto e : s){cout << e << " ";}cout << endl;set<string> strset = { "sort", "insert", "add" };// 遍历string⽐较ascll码⼤⼩顺序遍历的for (auto& e : strset){cout << e << " ";}cout << endl;return 0;
}

在这里插入图片描述

6、find和erase使用样例

#include<iostream>
#include<set>
using namespace std;
int main()
{set<int> s = { 4,2,7,2,8,5,9 };for (auto e : s){cout << e << " ";}cout << endl;// 删除最小值。删除的是搜索二叉树中的最左结点,即值最小结点s.erase(s.begin());for (auto e : s){cout << e << " ";}cout << endl;// 直接删除xint x;cin >> x;int num = s.erase(x);// 返回值是整型是为了兼容multiset中erase的用法if (num == 0){cout << x << "不存在!" << endl;}for (auto e : s){cout << e << " ";}cout << endl;// 或直接查找再利用迭代器删除xcin >> x;auto pos = s.find(x);if (pos != s.end()){s.erase(pos);}else{cout << x << "不存在!" << endl;}for (auto e : s){cout << e << " ";}cout << endl; // 两个查找的区别:// 算法库的查找是暴力查找 O(N),算法库里的find是为了vector、list这样的容器准备的auto pos1 = find(s.begin(), s.end(), x);// set自身实现的查找 O(logN)auto pos2 = s.find(x);// 利用count间接实现快速查找cin >> x;/*auto pos = s.find(x);if (pos != s.end())*/if (s.count(x)){cout << x << "存在" << endl;}else{cout << x << "不存在" << endl;}return 0;
}

在这里插入图片描述


把一段闭区间给lower-bound(下限)、upper-bound(上限),它们可以利用迭代器找一段set容器里的左闭右开的区间。
在这里插入图片描述

#include<iostream>
#include<set>
using namespace std;
int main()
{std::set<int> myset;for (int i = 1; i < 10; i++)myset.insert(i * 10); // 10 20 30 40 50 60 70 80 90for (auto e : myset){cout << e << " ";}cout << endl;// 实现查找到的[itlow, itup)包含[30, 60]区间// 返回 >= 30auto itlow = myset.lower_bound(30);// 返回 > 60auto itup = myset.upper_bound(60);// 删除这段区间的值myset.erase(itlow, itup);for (auto e : myset){cout << e << " ";}cout << endl;return 0;
}

在这里插入图片描述

// 实现查找到的[itlow, itup)包含[35, 65]区间
// 返回 >= 35
auto itlow = myset.lower_bound(35);
// 返回 > 65
auto itup = myset.upper_bound(65);
// 删除这段区间的值
myset.erase(itlow, itup);
for (auto e : myset)
{cout << e << " ";
}
cout << endl;

在这里插入图片描述

// 实现查找到的[itlow, itup)包含[35, 90]区间
// 返回 >= 35
auto itlow = myset.lower_bound(35);
// 返回 > 90,返回的是end()
auto itup = myset.upper_bound(90);
// 删除这段区间的值
myset.erase(itlow, itup);
for (auto e : myset)
{cout << e << " ";
}
cout << endl;

在这里插入图片描述

7、multiset和set的差异

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

#include<iostream>
#include<set>
using namespace std;
int main()
{// 相⽐set不同的是,multiset是排序,但是不去重multiset<int> s = { 4,2,7,2,4,8,4,5,4,4,9 };auto it = s.begin();while (it != s.end()){cout << *it << " ";++it;}cout << endl;// 相比set不同的是,x可能会存在多个,find查找中序的第一个xint 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;// 删除中序的第一个x/*pos = s.find(x);if (pos != s.end())s.erase(pos);*/// 相⽐set不同的是,erase给值时会删除所有的xs.erase(x);for (auto e : s){cout << e << " ";}cout << endl;cout << s.erase(5) << endl;cout << s.erase(6) << endl;return 0;
}

在这里插入图片描述

大量的数据排序用multiset还是不及放到vector里面走sort,虽然时间复杂度是O(logN),但是树形结构、链式结构的空间缓存命中率是不及vector的。

8、算法题

349.两个数组的交集
题解:两个数组的交集

142.环形链表Ⅱ
题解:环形链表Ⅱ

三、map系列的使用

1、map和multimap参考文档

< map >

2、map类的介绍

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

在这里插入图片描述

3、pair类型介绍

对比之前学习的搜索二叉树,之前两个值是分开存储的,但是map这里是一起存储的。分开存储的话,迭代器没办法解引用,*it调用operator*也不能返回两个值,只能一次返回一个值,两个值在一个结点中以一个结构体存储,这个结构体就是pair。pair是类模板的结构体。

map底层的红黑树节点中的数据,使用pair<Key, T>存储键值对数据。

在这里插入图片描述

4、map的构造

map的构造我们关注以下几个接口即可。

map的支持正向和反向迭代器遍历,遍历默认按key的升序顺序,因为底层是二叉搜索树,迭代器遍历走的中序;支持迭代器就意味着支持范围for,map支持修改value数据,不支持修改key数据,因为修改关键字数据,会破坏了底层搜索树的结构。

在这里插入图片描述

5、map的增删查

map的增删查关注以下几个接口即可:

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

在这里插入图片描述

6、map的数据修改

前面提到map支持修改 mapped_type 数据,不支持修改 key_type 数据,因为修改关键字数据,会破坏了底层搜索树的结构。

map第一个支持修改的方式是通过迭代器,迭代器遍历是通过find返回key所在的iterator修改,map还有一个非常重要的修改接口operator[],但是operator[]不仅仅支持修改,还支持插入数据和查找数据,所以他是一个多功能复合接口。

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

在这里插入图片描述

7、构造遍历及增删查使用样例

有了隐式类型转换以后就不会用make_pair了,写有名/匿名对象太麻烦就会用make_pair,make_pair是一个函数模板,本质就是一个推导,靠传递过来的参数推导类型而不用自己声明模板参数,最后返回pair的匿名对象。

在这里插入图片描述

#include <iostream>
#include <map>
using namespace std;
int main()
{// initializer_list构造及迭代遍历map<string, string> dict = { { "left", "左边" } , { "right", "右边" } , { "insert", "插入" } , { "string", "字符串" } };// map<string, string>::iterator it = dict.begin();auto it = dict.begin();while (it != dict.end()){/* 按照之前的写法会编译不通过,因为pair不支持流插入、流提取,pair里面的两个值是公有的,可以直接取first和second */// cout << (*it).first << ":" << (*it).second << endl;// map的迭代器基本都使用operator->,这里省略了一个->// 第一个->是迭代器运算符重载,返回pair*,第二个箭头是结构指针解引用取pair数据// cout << it.operator->()->first << ":" << it.operator->()-> second << endl;cout << it->first << ":" << it->second << endl;++it;}cout << endl;/* insert插入pair对象的4种方式,对比之下,最后一种最方便 */// 1、有名对象——最麻烦pair<string, string> kv1( "sort", "排序" );dict.insert(kv1);// 2、匿名对象——较麻烦dict.insert(pair<string, string>( "first", "第一个" ));// 3、make_pair——写有名/匿名对象太麻烦就会用,有了隐式类型转换以后就不会用make_pair了dict.insert(make_pair("second", "第二个"));// 4、多(单)参数的隐式类型转换dict.insert({ "auto", "自动的" });// "left"已经存在,插入失败dict.insert({ "left", "左边,剩余" });// 支持迭代器遍历就可以支持范围for遍历for (const auto& e : dict){cout << e.first << ":" << e.second << endl;}cout << endl;string str;while (cin >> str){auto ret = dict.find(str);if (ret != dict.end()){cout << "->" << ret->second << endl;}else{cout << "无此单词,请重新输入!" << endl;}}// erase等接口跟set完全类似,这里就不演示讲解了return 0;
}

在这里插入图片描述

8、map的迭代器和[]功能样例

统计水果出现的次数(真实的统计次数是不会用二叉搜索树的)。

set的迭代器不能修改值,map的迭代器可以修改Value。

#include <iostream>
#include <map>
#include <string>
using namespace std;
int main()
{// 利用find和iterator修改功能,统计⽔果出现的次数// 只能用find,不能用count,因为下面涉及修改Value。string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉" };map<string, int> countMap;for (const auto& str : arr){// 先查找水果在不在map中// 1、不在,则水果是第一次出现,插入{ 水果,1 }// 2、在,则查找到的结点中水果对应的次数++auto ret = countMap.find(str);if (ret == countMap.end()){countMap.insert({ str, 1 });}else{ret->second++;}}for (const auto& e : countMap){cout << e.first << ":" << e.second << endl;}cout << endl;return 0;
}
// 上面的一段可以用下面的代码替换
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main()
{// 利用[]插⼊+修改功能,巧妙实现统计水果出现的次数string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉" };map<string, int> countMap;for (const auto& str : arr){// []先查找⽔果在不在map中// 1、不在,说明⽔果第⼀次出现,则插⼊{⽔果, 0},同时返回次数的引⽤,++⼀下就变成1次了// 2、在,则返回⽔果对应的次数++countMap[str]++;}for (const auto& e : countMap){cout << e.first << ":" << e.second << endl;}cout << endl;return 0;
}

在这里插入图片描述

#include <iostream>
#include <map>
#include <string>
using namespace std;
int main()
{map<string, string> dict;dict.insert(make_pair("sort", "排序"));// key不存在->插⼊ {"insert", string()}dict["insert"];// 插入+修改dict["left"] = "左边";// 修改dict["left"] = "左边、剩余";// key存在->查找cout << dict["left"] << endl;// 在这里insert一个相同的key值,value是否会被修改?——不会,还是要利用迭代器或者[]修改dict.insert({ "sort", "排序xxxx" });return 0;
}

在这里插入图片描述

在这里插入图片描述

9、multimap和map的差异

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

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

#include <iostream>
#include <map>
#include <string>
using namespace std;
int main()
{multimap<string, string> dict;dict.insert({ "sort", "排序" });dict.insert({ "sort", "排序" });dict.insert({ "sort", "排序xxx" });dict.insert({ "left", "左边" });//dict.erase("sort");// 用来找一段相等的区间auto itpair = dict.equal_range("sort");// 返回迭代器区间,左闭右开auto it = itpair.first;while (it != itpair.second){cout << it->first << ":" << it->second << endl;++it;}cout << endl;return 0;
}

在这里插入图片描述

10、算法题

138.随机链表的复制
题解:随机链表的复制

692.前K个高频单词
题解:前K个高频单词

http://www.lryc.cn/news/589227.html

相关文章:

  • Windows内核对象
  • 【AutoCAD保姆级安装教程】AutoCAD 2025 版详细图文下载安装教程
  • wkhtmltopdf导出pdf调试参数
  • 【08】MFC入门到精通——MFC模态对话框 和 非模态对话框 解析 及 实例演示
  • 农村养老模式:乡土智慧与时代创新的共生之路
  • Gitlab跑CICD的时候,maven镜像和pom.xml使用的maven版本冲突导致没办法build成功的解决方法
  • 【C#地图显示教程:实现鼠标绘制图形操作】
  • jmeter之随机读取csv文件
  • jmeter提取接口返回值,对比去重
  • 说下对mysql MVCC的理解
  • LeetCode|Day13|88. 合并两个有序数组|Python刷题笔记
  • 记录一条面试sql题目
  • 《每日AI-人工智能-编程日报》--2025年7月15日
  • vue实现el-table-column中自定义label
  • 对象数组列表转成树形结构--树形结构转成列表(处理菜单)
  • 牛客:HJ21 简单密码[华为机考][哈希]
  • T8332AD这是一款多拓扑芯片质量很硬朗?
  • 【jvm|基本原理】第四天
  • 力扣 hot100 Day45
  • LeetCode Hot100 【1.两数之和、2.两数相加、3.无重复字符的最长子串】
  • 拼多多笔试题目一
  • 人机协作系列(四)AI编程的下一个范式革命——看Factory AI如何重构软件工程?
  • 力扣——1071. 字符串的最大公因子
  • 基于Alpine构建MySQL镜像
  • sublime如何支持换行替换换行
  • PHP安全漏洞深度解析:文件包含与SSRF攻击的攻防实战
  • Azure FXmsv2 系列与 Azure FXmdsv2 系列虚拟机正式发布
  • 606. 二叉树创建字符串
  • Java全栈工程师面试实录:从电商支付到AI大模型的应用场景与技术栈解析
  • Android 获取 UserAgent (UA) 的三种方式深度解析:差异、风险与最佳实践