C++map、set

devtools/2024/12/26 4:45:35/

1.引言

不同于之前介绍过的string、vector、list、deque、等容器,它们在逻辑结构上是线性的,并且两个位置存储的值之间没有紧密的关联,比如说交换或修改一下,还是原来的容器结构;今天要介绍的map和set在逻辑结构上是非线性的。容器可以分为两种:序列式容器关联式容器

  • 序列式容器:序列式容器也叫做顺序容器,其在逻辑结构上是线性的,两个位置存储的值之间一般没有紧密的关联关系,比如说交换一下或修改某个值,不影响容器,还是序列式的容器;序列式容器中的元素是按他们在容器中的存储位置来顺序保存和访问的。如string、vector、list、deque等。
  • 关联式容器:关联式容器不同于序列式容器,关联式容器逻辑结构通常是非线性结构,两个位置有紧密的关联关系,交换或者修改(修改对于key/value的场景可以修改value)都会破坏原来的存储结构;关联式容器中的元素是按关键字来保存和访问的。如map/set和unordered_map和unordered_set。
map和set底层是红黑树,红黑树是一颗平衡二叉搜索树。set是key搜索场景的结构,map是key/value搜索场景的结构。

2.set的使用

2.1set类

set的声明如下:
  • T就是存储的类型
  • Compare是仿函数,默认是小于比较,如果不支持或者说想自己控制比较的逻辑,可以通过仿函传给第二个参数
  • set底层存储数据的内存是从空间配置器申请的,需要的时候可以自己实现内存池传递给第三个参数
template < class T,                        // set::key_type/value_typeclass Compare = less<T>,        // set::key_compare/value_compareclass Alloc = allocator<T>      // set::allocator_type> class set;

set底层是用红黑树实现的,增删查效率是O(logN),迭代器遍历走的是搜索树的中序,所以是有序的;并且set是不支持插入相同值的,也就是说set中的值都是唯一的,multiset允许插入相同值。

2.2set的构造和迭代器

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

	set<int> s0 = { 6,2,4,5,1,9 };// initializer_list构造vector<string> v = { "hello", "welcome" ,"to" ,"programing" ,"world" };set<string> s1(v.begin(), v.end());// 迭代器区间构造set<int>::iterator i0 = s0.begin();while (i0 != s0.end()){// error C3892: “i0”: 不能给常量赋值//*i0 = 1;cout << *i0 << " ";i0++;}cout << "\n";for (auto a : s1){cout << a << "  ";}cout << "\n";

2.3set的增删查

// 单个数据插入,如果已经存在则插入失败
pair<iterator,bool> insert (const value_type& val);
// 列表插入,已经在容器中存在的值不会插入
void insert (initializer_list<value_type> il);
// 迭代器区间插入,已经在容器中存在的值不会插入
template <class InputIterator>
void insert (InputIterator first, InputIterator last);
// 查找val,返回val所在的迭代器,没有找到返回end()
iterator find (const value_type& val);
// 查找val,返回Val的个数
size_type count (const value_type& val) const;
// 删除一个迭代器位置的值
iterator erase (const_iterator position);
// 删除val,val不存在返回0,存在返回1
size_type erase (const value_type& val);
// 删除一段迭代器区间的值
iterator erase (const_iterator first, const_iterator last);
// 返回大于等val位置的迭代器
iterator lower_bound (const value_type& val) const;
// 返回大于val位置的迭代器
iterator upper_bound (const value_type& val) const;

实列:

	set<int,greater<int>> s = { 1,6,2,8,4};s.insert(3);s.insert({ 5,7,9,2});// 2插入失败for (auto a : s){cout << a << "  ";}cout << "\n";int x;cin >> x;int ret_erase = s.erase(x);if (ret_erase == 0){cout << "无此数据" << endl;}else{cout << "删除成功" << endl;for (auto a : s){cout << a << "  ";}cout << "\n";}cin >> x;set<int>::iterator ret_find = s.find(x);if (ret_find != s.end())// 不加以检查直接删除的话,遇到不存在的值可能会导致程序崩溃{s.erase(ret_find);cout << "删除成功" << endl;for (auto a : s){cout << a << "  ";}cout << "\n";}elsecout << "无此数据" << endl;set<string> sr= { "abandon","apple","cat" ,"dog" ,"erase","insert" ,"sotr" };string str;cin >> str;//auto ret = find(sr.begin(),sr.end(),str); // 算法库的查找是一个一个查找,时间复杂度是O(N)auto ret = sr.find(str);// set自己的查找是通过比较查找,时间复杂度是O(logN)if (ret != sr.end())cout << str << "在" << endl;elsecout << str << "不在" << endl;// 还可以通过count实现间接查找(str不在的话就是0个)cin >> str;if (sr.count(str))cout << str << "在" << endl;elsecout << str << "不在" << endl;

2.4set的lower_bound和upper_bound

算法库中也有lower_bound和upper_bound,不过要求要是有序的才能使用,set本身就是有序的,其lower_bound和upper_bound的使用如下:
	srand(time(nullptr));set<int> s;s.insert({ 15,20,21 });for (int i = 0; i < 10; i++){s.insert(rand() % 21 + 1);}for (auto a : s){cout << a << "  ";}cout << "\n";// [lower,upper)// lower是返回第一个大于等于15的迭代器// upper是返回第一个大于20的迭代器set<int>::iterator lower = s.lower_bound(15);set<int>::iterator upper = s.upper_bound(20);// 在C++中传递迭代器的时候都是左闭右开,所以这里会把 [15,21) 的都删除s.erase(lower, upper);for (auto a : s){cout << a << "  ";}cout << "\n";

2.5multiset和set

multiset和set的使用基本完全类似,主要区别就是multi支持值冗余,因此,insert、find、count、erase都会因为值冗余而有所差异
	multiset<int> s = { 3,5,2,0,3,2,0,1,2,1,0,2 };// multiset不会去重,但是还是能排序for (auto a : s){cout << a << "  ";}cout << "\n";// 不同于set,x可能会存在多个,multiset.find查找中序的第一个xint x;cin >> x;auto ret = s.find(x);while (ret != s.end() && *ret == x){cout << *ret << " ";ret++;}cout << "\n";// multiset会返回x的实际个数cout << "有" << s.count(x) << "个" << x << endl;// multiset会删除所有的xs.erase(x);for (auto a : s){cout << a << "  ";}cout << "\n";

3.map的使用

3.1map类

map是key/value的场景,声明如下:

  • Key就是底层key关键字类型,T是底层value的类型
  • 同样的,默认要求支持小于比较,如果不支持或者说想自己控制比较的逻辑,可以通过仿函传给第二个参数
  • map的底层存储数据结构的内存也是从空间配置器申请的
template < class Key,                                     // map::key_typeclass T,                                       // map::mapped_typeclass Compare = less<Key>,                     // map::key_compareclass Alloc = allocator<pair<const Key,T> >    // map::allocator_type> class map;

map底层是红黑树,增删查改效率是O(logN),迭代器走的也是中序,是按照key的顺序遍历的;同样也有multimap,才能插入相等值。

3.2pair类型

map底层的红黑树中的结点数据是一个个的pair<Key,T>,所以先来看一下pair,结构如下:

    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的简单使用:

	pair<string, string> p0("hello", "world");cout << p0.first << " " << p0.second <<  endl;

3.3map的构造

map支持正向反向迭代器,默认按照Key的升序顺序, map支持修改value数据,不支持修改Key数据,Key被修改,就破坏了底层搜索树的结构。
// empty (1) 无参默认构造
explicit map(const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type());
// range (2) 迭代器区间构造
template <class InputIterator>
map(InputIterator first, InputIterator last,const key_compare& comp = key_compare(),const allocator_type & = allocator_type());
// copy (3) 拷贝构造
map(const map& x);
// initializer list (5) initializer 列表构造
map(initializer_list<value_type> il,const key_compare & comp = key_compare(),const allocator_type & alloc = allocator_type());
// 迭代器是一个双向迭代器
iterator->a bidirectional iterator to const value_type
// 正向迭代器
iterator begin();
iterator end();
// 反向迭代器
reverse_iterator rbegin();
reverse_iterator rend();

3.4map的增删查

map的insert不同于set直接插入key, map是插入一个pair,find和erase和set类似,但是map的find是返回iterator,不仅可以确定key在不在,还能找到key映射的value,同时还能通过iterator修改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<string, string> dict = { {"key","钥匙"},{"lamp","灯"},{"mountain","山"},{"notebook","笔记本"},{"ocean","海洋"},{"pen","笔"}};auto it = dict.begin();while (it != dict.end()){cout << it->first << "的中文是:" << it->second << endl;// 实际上是下面通过调用operator->的返回值当作指针使用// 第一个->是迭代器运算符重载,返回pair*,第二个箭头是结构指针解引用取pair数据//cout << it.operator->()->first << "的中文是:" << it.operator->()->second << endl;++it;}cout << "=========================" << endl;// 最后一种最方便pair<string, string> p = { "book","书" };dict.insert(p);//插入一个pairdict.insert(pair<string, string>("mirror", "镜子"));//匿名对象pairdict.insert(make_pair("flower", "花朵"));//通过make_pair插入dict.insert({ "umbrella", "伞" });//隐式类型类型转换插入for (auto e : dict){cout << e.first << "的中文是:" << e.second << endl;}string str;while (cin >> str){auto ret = dict.find(str);if (ret!= dict.end())cout << str << "的中文是" << ret->second << endl;elsecout << "未收录此单词" << endl;}

3.5map的修改

map支持修改mapped_value数据(也就是value),不支持修改key数据。
map的修改方式有两种,一种是iterator遍历或者查找时返回所在的iterator修改另一种也是非常重要的一个修改接口就是operator[ ],operator[ ]不同于以前所学习的,不仅仅是简单的访问,map的operator[ ]不仅仅支持修改,还支持插入数据查找数据,是一个多功能复合接口。

map对find的声明如下:

// map中的成员变量
Member types
key_type->The first template parameter(Key)
mapped_type->The second template parameter(T)
value_type->pair<const key_type, mapped_type>iterator find(const key_type& k);
pair<iterator, bool> insert(const value_type& val);
mapped_type& operator[] (const key_type& k)
{pair<iterator, bool> ret = insert({ k, mapped_type() });iterator it = ret.first;return it->second;
}

1、find查找k的时候,返回k所在的迭代器,没有找到就返回end(),如果找到了可以通过iterator修改对应的mapped_type值。

2、insert插入一个pair<key,T>对象,返回的也是一个pair对象,但是返回对象分如下情况

  • 如果key已经在map中,插入失败,first是key所在结点的迭代器,second是false
  • 如果key不在map中,插入成功,first是新插入的key所在的结点的迭代器,second是true

也就是说,无论插入成功还是失败,返回pair<iterator,bool>对象的first都会指向key所在的迭代器(只是一个是已存在的,另一个是新插入的)

3、operator[k]的时候,也分为两种情况

  • k不在map中,insert会插入k和mapped_type默认值,同时返回结点中存储的mapped_type值的引用,这样就能通过引用修改值,所以说具备插入+修改的功能
  • k在map中,insert会插入失败,但是insert返回的pair对象中的first是指向key结点的迭代器,返回值同时返回[ ]返回结点中存储的mapped_type的值的引用,所以具备查找+修改的功能(operator[k]的时候,要注意有副作用:比如说不在map中会导致pair的second是mapped_value默认值)

3.6map的迭代器和operator[ ]

string array[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉" , "香蕉", "苹果", "香蕉" , "香蕉", "苹果", "香蕉" };
map<string, int>count;
for (const auto& e : array)
{auto ret = count.find(e);// 通过find返回的iterator实现计数if (ret == count.end()){count.insert({ e,1 });}else{ret->second++;}
}
for (const auto& e : array)
{// 通过[]巧妙统计次数count[e]++;
}
for (const auto e : count)
{cout << e.first << ":" << e.second << endl;
}
cout << "\n";

3.9multimap和map

multimap和map的使用基本相同,主要区别是multimap支持关键值key冗余,那么insert、find、count、erase都会围绕着支持冗余而有所差异;find的时候,有多个key,返回中序的第一个;erase的时候,会删除所有的key;要注意multimap不支持[ ],因为支持key冗余,[ ]就只能支持插入了,不能支持修改。

http://www.ppmy.cn/devtools/145443.html

相关文章

JAVA HTTP压缩数据

/*** 压缩数据包** param code* param data* param resp* throws IOException*/protected void writeZipResult(int code, Object data, HttpServletResponse resp) throws IOException {resp.setHeader("Content-Encoding", "gzip");// write到客户端resp…

IDEA使用Alt + Enter快捷键自动接受返回值一直有final修饰的问题处理

在使用IDEA的过程中&#xff0c;使用快捷键Alt Enter在接收返回值时&#xff0c;可以快速完成参数接收&#xff0c;但前面一直会出现接收参数前面有final修饰的情况&#xff0c;效果如下所示&#xff1a; 看着真烦人呢&#xff0c;我们会发现在接受到返回值是上方有个 Declare…

Springboot 整合 Duird

Springboot 整合 Duird 1. pom.xmlyml配置启动类测试手动JDBC&#xff0c;执行动态sql启动日志Duird 监控地址SQL监控 1. pom.xml <dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version&g…

SMMU软件指南SMMU编程之虚拟机结构和缓存

安全之安全(security)博客目录导读 目录 一、虚拟机结构(VMS) 二、缓存 一、虚拟机结构(VMS) 虚拟机结构(VMS)是SMMU中的概念,是一个由STE.VMSPtr字段指向的结构,包含每个虚拟机的配置设置。在相同安全状态下具有相同虚拟机ID(VMID)的多个STE必须指向相同的VMS。…

大语言模型中的Agent优势及相关技术;Agent和RAG区别

大语言模型中的Agent优势及相关技术: 强大的任务规划与执行能力 技术:通过将复杂任务拆解为多个子任务,并依据任务间的逻辑关系和优先级进行规划,确定执行顺序,调用相应工具或模型来完成各子任务,最终实现复杂任务的整体解决。如微软的Jarvis,可利用LLM的推理规划能力拆…

重温设计模式--外观模式

文章目录 外观模式&#xff08;Facade Pattern&#xff09;概述定义 外观模式UML图作用 外观模式的结构C 代码示例1C代码示例2总结 外观模式&#xff08;Facade Pattern&#xff09;概述 定义 外观模式是一种结构型设计模式&#xff0c;它为子系统中的一组接口提供了一个统一…

【优选算法】快乐数

链接&#xff1a;202. 快乐数 - 力扣&#xff08;LeetCode&#xff09; 算法原理&#xff1a; 鸽巢原理&#xff08;抽屉原理&#xff09;&#xff1a;n个巢穴&#xff0c;n1个鸽子&#xff0c;至少有一个巢穴里的鸽子 > 1。 可以抽象为&#xff1a;判断链表是否有环&…

VMWare 的克隆操作

零、碎碎念 VMWare 的这个克隆操作很简单&#xff0c;单拎出来成贴的目的是方便后续使用。 一、操作步骤 1.1、在“源”服务器上点右键&#xff0c;选择“管理--克隆” 1.2、选择“虚拟机的当前状态”为基础制作克隆&#xff0c;如下图所示&#xff0c;然后点击“下一页” 1.3、…