C++之set/multiset 和 map/multimap的使用

关联式容器

在初阶阶段,我们已经接触过STL中的部分容器:

比如:vector、list、deque、forward_list(C++11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。

今天要了解的几个容器称为关联式容器:

关联式容器也是用来存储数据的, 与序列式容器不同的是, 其里面存储的是<key, value>结构的键值对, 在数据检索时比序列式容器效率更高。


 键值对

用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代
表键值,value表示与key对应的信息。比如: 现在要建立一个英汉互译的字典, 那该字典中必然
有英文单词与其对应的中文含义, 而且, 英文单词与其中文含义是一一对应的关系, 即通过该应
该单词, 在词典中就可以找到与其对应的中文含义。

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){}
};

pair的应用:

pair是将2个数据组合成一个数据,当需要这样的需求时就可以使用pair。
(1)STL中的map就是将key和value放在一起来保存(一般first对应key,second对应value)
(2)当一个函数需要返回2个数据的时候,可以选择pair

所以可以认为pair就是库里面提供的一个键值对的类。


树形结构的关联式容器

根据应用场景的不同,STL总共实现了两种不同结构的关联式容器:  树型结构与哈希结构

树型结构的关联式容器主要有四种:

map、set、multimap、multiset

这四种容器的共同点是: 使用平衡二叉搜索树(即红黑树)作为其底层结构, 容器中的元素是一个有序的序列, 普通的搜索二叉树如果数据有序或接近有序二叉搜索树将退化为单支树, 查找元素相当于在顺序表中搜索元素, 效率低下。那平衡二叉树顾名思义就是在普通搜索二叉树的基础上让它变的平衡(需要对树中的结点进行调整)。


set

认识set

文档介绍

首先我们来看一下set:

set是一个类模板, 有三个模板参数, 但平时使用的时候一般只需要管第一个模板参数, 后两个都是有缺省值的, 如果想改变它底层搜索树的排序方式你可以自己传第二个比较方式的仿函数(默认是升序)。 

set的介绍

1. set是按照一定次序存储元素的容器
2. 在set中, 元素的value也标识它(value就是key,类型为T), 并且每个value必须是唯一的。
set中的元素不能在容器中修改(元素总是const),但是可以从容器中插入或删除它们。
3. 在内部, set中的元素总是按照其内部比较对象(类型比较)所指示的特定严格弱排序准则进行排序。
4. set容器通过key访问单个元素的速度通常比unordered_set容器慢,但它们允许根据顺序对
子集进行直接迭代。
5. set在底层是用二叉搜索树(红黑树)实现的。
注意:
1. 与map/multimap不同,map/multimap中存储的是真正的键值对<key, value>,set中只放
value,但在底层实际存放的是由<value, value>构成的键值对。
2. set中插入元素时,只需要插入value即可,不需要构造键值对。
3. set中的元素不可以重复(因此可以使用set进行去重)。
4. 使用set的迭代器遍历set中的元素,可以得到有序序列
5. set中的元素默认按照小于来比较
6. set中查找某个元素,时间复杂度为:O(logn)
7. set中的元素不允许修改
8. set中的底层使用二叉搜索树(红黑树)来实现。 

那我们上面说到:

这几个关联式容器它们的底层结构其实就是我们之前学的二叉搜索树, 那set其实就对应我们前面学的搜索二叉树的应用里面的K模型,下面要学的map就对应KV模型。


set的使用 

 构造函数

构造空set然后插入元素: 
#include<set>
#include <iostream>
using namespace std;
void testset1()
{set<int> s;s.insert(4);s.insert(6);s.insert(3);s.insert(5);s.insert(2);s.insert(1);s.insert(1);s.insert(1);for (set<int>::iterator it = s.begin(); it != s.end(); it++){cout << *it << " ";}cout << endl;
}int main()
{testset1();return 0;
}

迭代器区间构造: 
void testset1()
{vector<int> v = { 4,6,3,5,2,1,1,1 };set<int> s(v.begin(),v.end());for (set<int>::iterator it = s.begin(); it != s.end(); it++){cout << *it << " ";}cout << endl;
}int main()
{testset1();return 0;
}

但是我们看到这里打印出来是1,2,3,4,5,6。

我们按照无序插入并且插入了3个1, 它的底层是搜索二叉树, 搜索二叉树一般是不能有重复值的, 所以如果多次插入相同的值, 插入就会失败(或者可以理解成它会自动进行去重),另外我们知道二叉搜索树也叫做二叉排序树,中序遍历就可以得到一个升序序列,所以这里默认遍历打印出来就是有序了。所以可以认为set可以实现排序+去重

可以看到不能对set内的元素进行修改, 它的底层是搜索树, 那搜索树任意修改里面的值的话,修改之后不能保证它还是一棵搜索树,所以不能修改。 


find与count

find可以搭配erase一块使用: 

void testset1()
{vector<int> v = { 4,6,3,5,2,1,1,1 };set<int> s(v.begin(),v.end());set<int>::iterator it = s.find(3);if (it != s.end()){s.erase(it);}for (set<int>::iterator it = s.begin(); it != s.end(); it++){cout << *it << " ";}cout << endl;
}

 

其实还可以用另一个接口——count 

count是统计元素的个数,但是set里面不允许重复值,所以结果只有1和0,存在就是1,不存在就是0 

void testset2()
{vector<int> v = { 4,6,3,5,2,1,1,1 };set<int> s(v.begin(), v.end());if (s.count(3))cout << "3在" << endl;elsecout << "3不在" << endl;
}int main()
{testset2();return 0;
}


erase

void testset3()
{std::set<int> myset;for (int i = 1; i < 10; i++) myset.insert(i * 10);  // 10 20 30 40 50 60 70 80 90std::set<int>::iterator it=myset.begin();myset.erase(it);//删除10myset.erase(40);//删除40it = myset.find(60);myset.erase(it, myset.end());//删除60-90cout << "myset contains:";for (it = myset.begin(); it != myset.end(); ++it)cout << ' ' << *it;cout << '\n';
}

 


lower_bound与upper_bound

 

void testset4()
{std::set<int> myset;std::set<int>::iterator itlow, itup;for (int i = 1; i < 10; i++) myset.insert(i * 10); // 10 20 30 40 50 60 70 80 90itlow = myset.lower_bound(25);                // 大于等于val值位置的iterator,itlow指向30itup = myset.upper_bound(70);                 // 大于val值位置的iterator,itup指向80// [25, 70]myset.erase(itlow, itup);                     // 10 20 80 90for (auto e : myset){cout << e << " ";}cout << endl;
}

 eual_range:

 返回一个pair,pair.first是lower_bound(val),pair.second是upper_bound(val).

由于set元素是唯一的, 所以区间长度最长就是1

void testset5()
{std::set<int> myset;for (int i = 1; i <= 5; i++) myset.insert(i * 10);   // myset: 10 20 30 40 50std::pair<std::set<int>::const_iterator, std::set<int>::const_iterator> ret;ret = myset.equal_range(35);std::cout << "the lower bound points to: " << *ret.first << '\n';   // >= valstd::cout << "the upper bound points to: " << *ret.second << '\n';  // > valcout << endl;ret = myset.equal_range(30);std::cout << "the lower bound points to: " << *ret.first << '\n';   // >= valstd::cout << "the upper bound points to: " << *ret.second << '\n';  // > val
}


multiset 

那在<set>这个头文件里面还有一个multiset,它跟set最大的区别就是允许里面存的key值冗余, 可以认为它有排序, 但没进行去重.

那允许键值冗余的话它的插入怎么做呢?

那就接着插就行了, 即使插入的是已经存在的值, 那搜索树的话我们说大的值要插入到右子树,小的插入到左子树, 那现在相等的话其实插入到哪边都可以, 可以左边也可以右边, 反正平衡二叉树插入之后会对结点进行调整使得两边平衡

 find:

如果我们在multiset中find某个值的话,如果有多个相同的值,那find的时候查找的是哪一个?
规定它find的是中序遍历遇到的第一个,它遍历的时候底层走的是中序遍历,所以打印出来才是有序的。

void testset6()
{// 排序multiset<int> s;s.insert(5);s.insert(2);s.insert(6);s.insert(1);s.insert(1);s.insert(2);s.insert(1);s.insert(5);s.insert(2);cout << "multiset contains:" << endl;for (auto e : s){cout << e << " ";}cout << endl<<endl;// 如果有多个值,find返回中序第一个valcout << "multiset begins with 2:" << endl;multiset<int>::iterator it = s.find(2);while (it != s.end()){cout << *it << " ";++it;}cout << endl<<endl;cout << "the number of 1: ";cout << s.count(1) << endl << endl;// [>=val, >val)auto ret = s.equal_range(2);s.erase(ret.first, ret.second);cout << "after erasing 2, multiset contains:"<< endl;for (auto e : s){cout << e << " ";}cout << endl << endl;size_t n = s.erase(1);cout << "the number of 1: " << n;cout << "after erasing 1, multiset contains:" << endl;for (auto e : s){cout << e << " ";}cout << endl << endl;
}

map

认识map

文档介绍

map其实就对应搜索二叉树的KV模型,它里面存的是<key, value>结构的键值对。即用来表示具有一 一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息。

 

1. map是关联容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元素。
2. 在map中, 键值key通常用于排序和唯一地标识元素, 而值value中存储与此键值key关联的
内容。键值key和值value的类型可能不同, 并且在map的内部, key与value通过成员类型
value_type绑定在一起,为其取别名称为pair: typedef pair<const key, T> value_type;
3. 在内部,map中的元素总是按照键值key进行比较排序的。
4. map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序
对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列)。
5. map支持下标访问符,即在[]中放入key,就可以找到与key对应的value。
6. map通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树))。 

map的使用 

insert: 

 

可以看到插入的是value_type类型的值, 而value_type是一个pair,pair.first(key)是const修饰的,不能修改(即键必须是唯一的),pair.second(value)可以。 

void test_map1()
{map<string, string> dict;dict.insert(pair<string, string>("sort", "排序"));dict.insert(pair<string, string>("insert", "插入"));dict.insert(make_pair("right", "右边")); // 推荐这个dict.insert(make_pair("string", "字符串"));dict.insert(make_pair("string", "字符串11111"));for (map<string, string>::iterator it = dict.begin(); it != dict.end(); it++){cout << it->first << ":" << it->second << endl;}
}

这个make_pair是什么呢?它其实是库里面提供的一个函数模板, 它可以帮助我们创建一个pair的对象, 用它的好处是我们不需要自己去指定类型, 因为模板可以自动推导类型。 

此外,map里键必须是唯一的不能重复,值可以重复, 一个键只能对应一个值,一个值可以对应多个键 

insert的返回值: 

 insert会返回一个pair:

pair.second是bool值,插入成功bool为true,失败为false.

pair.first是一个迭代器, 插入成功迭代器指向新插入的这个键值对, 失败的话指向已经存在的那个键值对.

void test_map1()
{map<string, string> dict;dict.insert(pair<string, string>("sort", "排序"));dict.insert(pair<string, string>("insert", "插入"));dict.insert(make_pair("right", "右边")); // 推荐这个dict.insert(make_pair("string", "字符串"));auto p = dict.insert(make_pair("string", "字符串11111"));cout << p.first->first << ":" << p.first->second << endl;
}

 

还要注意一个问题: 

 

insert的第一个模板参数是const key_type: 但是这段插入的是pair<string,string>类型的pair, 第一个参数并没有const修饰,为什么还能插入?

所以这个pair类型即使有差异也可以进行拷贝构造, 比如这样也行: 

所以只要这两个U和V(这里都是const char*)能去初始化this->first和this->second, 都是可以拷贝的, 所以这个拷贝构造 可以是构造也可以是拷贝构造:

当U和V与this->first和this->second类型相同时, 就是拷贝构造, 类型不同但是可以转化的, 就是构造.

 这样就更方便地解释了为什么推荐用make_pair:

make_pair是函数模板, 可以自己推演模板参数, 而之前用pair<string,string>需要手动把模板类型写出来(显示实例化):

所以make_pair之后不管是pair<const char*, const char*> 还是 pair<stirng,stirng>类型, 都不用管, 最后都会调用默认构造函数构造出pair<const string, string>. 

find:

和set的find类似, 找到返回该位置的迭代器, 找不到返回end 

用map写一下统计次数的程序: 

void test_map2()
{string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };map<string, int> countMap;for (auto& str : arr){auto ret = countMap.find(str);if (ret == countMap.end()){// 没找到表示第一次出现,插入countMap.insert(make_pair(str, 1));}else{// 次数++ret->second++;}}for (auto& kv : countMap){cout << kv.first << ":" << kv.second << endl;}
}

 

 map中[]的理解

 

所以上面的查找代码可以替换为: 

void test_map3()
{string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };map<string, int> countMap;for (auto& str : arr){countMap[str]++;}for (auto& kv : countMap){cout << kv.first << ":" << kv.second << endl;}
}

仔细观察一下operator[]:

 

 

文档上面也解释了,调用这个重载的[]就等效于执行这句代码

(*((this->insert(make_pair(k,mapped_type()))).first)).second

 这里面其实去调了一下insert,那我们来仔细研究一下insert的返回值

插入是有成功和失败两种情况, 因为键值不允许冗余, 它返回的是一个pair,其中第一个模板参数为迭代器, 第二个为bool。

当插入成功的时候,pair的first为指向新插入元素的迭代器,second为true,当插入失败的时候(其实就是插入的键已经存在了),那它的first为容器中已存在的那个相同的等效键元素的迭代器,second为false。所以后面这个bool其实是标识插入成功还是失败

那我们再来看这个函数: 

此外, 我们看到这里面insert也是使用make_pair这个函数创建一个pair对象, 第一个参数就是我们[]里面放的那个键key, 第二个参数传的是调了值value的类型的默认构造(内置类型也有构造函数), 那value是int,默认构造的值是0, 所以我们第一次插入刚好它的次数就是0了。然后返回这个次数的引用, 外面对它++, 就正好是1。
然后后续插入相同键的话,就插入失败, 不会将次数变成0,但是依然返回次数(对应pair中的second)的引用,我们从1继续往上++就行了

利用[]进行插入,修改,删除: 

void test_map4()
{map<string, string> dict;dict.insert(make_pair("sort", "排序"));dict.insert(make_pair("insert", "插入"));dict.insert(make_pair("right", "右边")); dict.insert(make_pair("string", "字符串"));dict["girl"];//插入dict["boy"] = "男孩";//插入并修改dict["girl"] = "女孩";//修改cout << dict["girl"] << endl;//查找
}

 

multimap

和set一样, map还有multimap

 

跟map相比,multimap允许键key冗余,所以它的接口里面就没有[] 

void test_map5()
{multimap<string, string> dict;dict.insert(make_pair("sort", "排序"));dict.insert(make_pair("insert", "插入"));dict.insert(make_pair("boy", "男孩"));dict.insert(make_pair("left", "左边"));dict.insert(make_pair("right", "右边"));dict.insert(make_pair("string","字符串1"));dict.insert(make_pair("string", "字符串2"));for (multimap<string, string>::iterator it = dict.begin(); it != dict.end(); it++){cout << it->first << ":" << it->second << endl;}
}

 

更多可以查阅文档

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/181983.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C/C++网络编程基础知识超详细讲解第三部分(系统性学习day13)

懒大王感谢大家的关注和三连支持~ 目录 前言 一、并发服务器 1.进程并发服务器 实例代码如下&#xff1a; 2.线程并发服务器 实例代码如下&#xff1a; 二、域通信 域通信TCP实例代码如下&#xff1a; 三、广播与组播(UDP) 1.广播 实例代码如下&#xff1a; …

《向量数据库指南》——开源框架NVIDIA Merlin 向量数据库Milvus

NVIDIA Merlin & Milvus 推荐系统 pipeline 中至关重要的一环便是为用户检索并找到最相关的商品。为了实现这一目标,通常会使用低维向量(embedding)表示商品,使用数据库存储及索引数据,最终对数据库中数据进行近似最近邻(ANN)搜索。这些向量表示是通过深度学习模型获…

STM32单片机在线升级,手机在线升级STM32单片机,固件远程下载方法,局域网在线程序下载

STM32单片机&#xff0c;是我们最常见的一种MCU。通常我们在使用STM32单片机都会遇到程序在线升级下载的问题。 STM32单片机的在线下载通常需要以下几种方式完成&#xff1a; 1、使用ST提供的串口下载工具&#xff0c;本地完成固件的升级下载。 2、自行完成系统BootLoader的编写…

使用阿里云服务器,httplib库在listen过程中,出现Cannot assign requested address错误???

今天&#xff0c;在做一个小项目的时候&#xff0c;使用httplib库进行建立tcp连接&#xff0c;但是一旦程序开始&#xff0c;并没有等待tcp连接的到来&#xff0c;而是直接结束了。 打印一下strerror(errno) 根本就没有进行客户端的连接。 找了一下午&#xff0c;检测是否…

大数据毕业设计选题推荐-收视点播数据分析-Hadoop-Spark-Hive

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

javaee实验:搭建maven+spring boot开发环境,开发“Hello,Spring Boot”应用

目录 mavenspringboot实验目的实验内容环境的搭建 在开发中&#xff0c;maven和spring都是非常常用、非常重要的管理工具和框架&#xff0c;今天就在这里使用idea进行环境的搭建和创建第一个spring程序 maven 1.1maven是一个跨平台的项目管理工具&#xff08;主要管理jar包&am…

【双指针+简化去重操作】Leetcode 15 三数之和

【双指针简化操作】Leetcode 15 三数之和 解法1 解法1 新建一个嵌套列表&#xff1a;List<List<Integer>> result new List<>(); 初始化一个ArrayList并直接赋值&#xff1a;ArrayList<Integer> result new ArrayList<>(Arrays.asList(1, 2…

Python库学习(十二):数据分析Pandas[下篇]

接着上篇《Python库学习(十一):数据分析Pandas[上篇]》,继续学习Pandas 1.数据过滤 在数据处理中&#xff0c;我们经常会对数据进行过滤&#xff0c;为此Pandas中提供mask()和where()两个函数&#xff1b; mask(): 在 满足条件的情况下替换数据&#xff0c;而不满足条件的部分…

Find My手机保护壳|苹果Find My与手机保护壳结合,智能防丢,全球定位

随着科技水平的快速发展&#xff0c;科技美容这一行业做为新型产业新生而出。时尚IT品牌随着市场的多元化发展。针对手机品牌和功能的增加而呈多样化&#xff0c;将手机保护壳按质地分有PC壳&#xff0c;皮革 &#xff0c;硅胶&#xff0c;布料&#xff0c;硬塑&#xff0c;皮套…

mac电脑大旧型文件清理软件CleanMyMac2024

CleanMyMac的大旧文件模块会帮您定位、检查和移除您几个月没有打开过并且不再需要的大型文件和文件夹&#xff0c;这样可以节省更多的磁盘空间。 CleanMyMac X全新版下载如下: https://wm.makeding.com/iclk/?zoneid49983 大型和旧文件模块可以查找和移除大型文件和文件夹&…

Python 海龟绘图基础教学教案(六)

Python 海龟绘图——第 9 题 题目&#xff1a;绘制下面的图形 解析&#xff1a; 综合应用&#xff0c;绘制长方形。 答案&#xff1a; 不使用循环。 Python 海龟绘图——第 10 题 题目&#xff1a;绘制下面的图形 解析&#xff1a; 综合命令使用。 答案&#xff1a; 使用循环…

Java VMTranslator Part II

用Java写一个翻译器&#xff0c;将Java的字节码翻译成汇编语言 目录 程序控制流开发 基本思路 核心代码 实验结果&#xff0c;使用例子进行验证 函数调用 基本思路 核心代码 实验结果&#xff0c;使用例子进行验证 Parser CodeWriter Main 程序控制流开发 基本思路…

2.JMeter压测接口

概述 今日目标&#xff1a; JMeter使用 配置线程组配置 HTTP 接口配置断言 配置响应断言配置断言响应时间 配置结果监听压测报告 接口准备聚合报告察看结果树其它 线程组配置详解 线程数Ramp-Up bug结束 JMeter使用 双击 ApacheJMeter.jar 启动&#xff0c;然后修改名称&a…

C 练习实例10 打印楼梯,同时在楼梯上方打印两个笑脸。

题目&#xff1a;打印楼梯&#xff0c;同时在楼梯上方打印两个笑脸。 程序分析&#xff1a;用 ASCII 1 来输出笑脸&#xff1b;用i控制行&#xff0c;j来控制列&#xff0c;j根据i的变化来控制输出黑方格的个数。 如果出现乱码情况请参考【C 练习实例7】的解决方法。 实例 …

第四届辽宁省大学生程序设计竞赛(正式赛)(12/13)

AC情况 赛中通过赛后通过暂未通过A√B√C√D○E○F√G√H√I○J√K—L√M√ 整体体验 easy&#xff1a;ABFHL mid&#xff1a;MJGC hard&#xff1a;IDKE 心得 感觉出了一堆典题&#xff0c;少数题还有些意思&#xff0c;E题确实神仙 题解 A. 欢迎来到辽宁省赛&#x…

iphone15 nplayer播放本地电影投屏天猫魔盒(电视)卡顿解决方案

文章目录 投屏环境现象写在前面 解决方案所需投屏app安装方法试用结果如果文章对您有用&#xff0c;欢迎收藏或关注&#xff01; iphone15 nplayer播放本地电影投屏天猫魔盒(电视)卡顿解决方案 投屏环境 全千兆wifi6局域网 1000兆电信宽带 天锚魔盒4Pro 8G&#xff08;M19&…

《研发效能(DevOps)工程师》课程简介(四)丨IDCF

由国家工业和信息化部教育与考试中心颁发的职业技术证书&#xff0c;也是国内首个研发效能&#xff08;DevOps&#xff09;职业技术认证&#xff0c;内涵1000页学习教材2000分钟的课程内容讲解460多个技术知识点300多道练习题。涵盖【组织与协作】、【产品设计与运营】、【开发…

YOLOv8-Seg改进:动态蛇形卷积(Dynamic Snake Convolution) | ICCV2023

🚀🚀🚀本文改进:动态蛇形卷积(Dynamic Snake Convolution),增强微小特征提取能力,引入到YOLOv8-Seg,与C2f结合实现二次创新 🚀🚀🚀Dynamic Snake Convolution亲测在番薯破损分割任务中,mask mAP@0.5 从原始的0.625提升至0.645 🚀🚀🚀YOLOv8-seg创新专…

namespace

1.namespace技术 namespace是Linux内核的一组特性&#xff0c;支持对内核资源进行分区隔离&#xff0c;让一组进程只能看到一组资源&#xff0c;而另一组进程只能看到另一组不同的资源。换句话说&#xff0c;namespace的关键特性是进程隔离。在运行许多不同服务的服务器上&…

2023.11.4 Idea 配置国内 Maven 源

目录 配置国内 Maven 源 重新下载 jar 包 配置国内 Maven 源 <mirror><id>alimaven</id><name>aliyun maven</name><url>http://maven.aliyun.com/nexus/content/groups/public/</url><mirrorOf>central</mirrorOf> …