C++相关概念和易错语法(23)(set、仿函数的应用、pair、multiset)

1.set和map存在的意义

(1)set和map的底层都是二叉搜索树,可以达到快速排序(当我们按照迭代器的顺序来遍历set和map,其实是按照中序来遍历的,是排过序的)、去重、搜索的目的。

(2)优先级队列priority_queue也有类似的功能,但是它的底层是数组,在插入删除频繁的情况下效率降低,且它的数据结构是堆,在排序上效率还行,但在搜索上就无能为力了。

(3)在C++数据结构重要知识点(1)我就讲过二叉搜索树的特性,在退化的情况下搜索效率低下,所以要引入AVL树、红黑树这样的平衡树来解决问题。set和map就引入了红黑树,使得这两个容器的搜索效率很高。

(4)set和map有什么区别呢?其实就是分别对应key和key-value模型。set是针对key单个数据的二叉搜索树,而map是针对key-value那样的键值对的二叉搜索树。

(5)两种容器

序列式容器:vector、list对存储数据的顺序的要求不高,注意这句话的意思是在序列式容器中换两个数据虽然可能会影响到它的功能(比如本来是降序排列的vector,现在被打乱了),但不影响容器结构本身,vector还叫vector
关联式容器:map、set对数据顺序有强关联性,结构靠数据支撑,如果你随便换两个数据的位置,那么整个容器就崩了,而且无法修复。

2.set

set的底层引入了红黑树,大致和key模型的二叉搜索树一样,是借助二叉树的特性来存放数据,达到排序和搜索的功能的。但是在接口上和我们上篇文章分享的又不一样

(1)模板参数、仿函数的应用

第一个模板参数:要存放数据的类型,可以是int这样的内置类型,也可以是自定义类型

第三个模板参数基本不用管

第二个模板参数:默认是less<T>,在用迭代器遍历时是从小到大,注意我们前面讲的优先级队列priority_queue默认也是less<T>,但对应的是大堆。我们也可以自己写一个仿函数作为set的第二个模板参数,自定义规则,不过自定义仿函数的坑有点多,下面分享一下需要注意的点,加深对仿函数的理解。

先看以下代码

#include <iostream>
#include <string>
#include <set>
using namespace std;template<class T>
struct Compare
{bool operator()(const T& t1, const T& t2) const{return t1 > t2;}
};template<>
struct Compare<string>
{bool operator()(const string& s1, const string& s2) const{return (s1.size() > s2.size()) || (s1.size() == s2.size() && s1 < s2);}
};int main()
{set<int, Compare<int>> s1;s1.insert(1);s1.insert(2);s1.insert(3);set<string, Compare<string>> s2;s2.insert("zzzzzzz");s2.insert("aaaaaaa");s2.insert("bb");for (const auto& e : s1){cout << e << " ";}cout << endl;for (const auto& e : s2){cout << e << " ";}return 0;
}

输出结果是

为什么对于s1是从大排到小呢?为什么s2是这样排的呢?它们是怎样控制的呢?

在默认的情况下是less,对应的是从小排到大,即小的元素在大的元素前面,因此我们可以这样分析

再分析我们的

在写仿函数的时候特别注意举一反三,我上面两张图都提到的“返回true是谁在前”并不适用于所有情况(map和set都遵循),返回true时到底是t1在前还是t2在前要自己判断。借助默认排序方式和仿函数类型可以判断,如果set默认仿函数是greater,而默认访问是从小到大,那么自己写仿函数时就应该遵循“返回true时是t2(第二个函数参数)在前”来写代码了。

我们并不知道STL里面到底是怎样排序的,所以从细节推理出排序结果很重要,看似很简单,但一定不能含糊。

还有个细节:在自己写仿函数时要把重载函数定义为const对象,否则是编译不通过的。

(2)构造函数

总体分为三类:空构造、拷贝构造、迭代器构造

最后的内存池相关的参数不管它,倒数第二个comp只能是显式实例化容器时使用的仿函数,不过一般也不写,因为编译器会自己生成对应的仿函数

(3)insert、pair

insert最常用的就是第一个,第二第三个基本不用。但是返回值pair<iterator, bool>是什么呢?

pair是一个模板类,叫键值对,它有两个成员变量,一个叫first,另一个叫second,first和second构成一一对应的关系,first相当于key,second相当于value,在map中也用到了它。

要创建一个pair对象也很简单

pair和我们之前学的容器不同,pair只是一个存储数据的类型,它的底层非常简单,它存在的意义就是将key和value联系起来,根本不存在增删查改。

作为一个专门用于存储数据的类型,它也有自己的判断大小的方式,也很好理解。当first和second都相同时pair相同,first大的pair就大,first都相同时second大的pair就大。

了解完pair之后我们可以去研究set的insert了

val就是我们想插入set的数据,那么返回的键值对有什么价值呢?

这也是set和map可以实现去重的原因之一,除此以外,像find之类的函数也可以通过insert变相实现了

(4)erase、count、multiset

常用的是第二种,直接删除某个值,返回值是删除的值的个数。这个时候我们就有疑问了,直接用bool不好吗,删除了就是true,删不掉就是false,这是为了multiset准备的

multiset是一个没有去重效果的set,可以用于除去重以外其他功能的实现

值得注意的是,multiset的大部分接口和set没什么两样,但是在set中有的接口设计会考虑去重,比如insert的返回值,而在multiset中这就没有必要了,所以存在一些不同之处

在find中multiset返回的就是第一个出现的val的迭代器

同样的,像count函数也存在erase类似处理的情况

count返回的是val在set中出现次数,就是为了统一set和multiset的接口用法

(5)lower_bound、upper_bound

这两个函数还是比较容易混的,我们先看看下面的代码


int main()
{set<int> s;s.insert(1);s.insert(2);s.insert(3);s.insert(4);s.insert(5);s.erase(s.lower_bound(2), s.upper_bound(4));for (const auto& e : s){cout << e << " ";}return 0;
}

输出结果是

lower_bound和upper_bound返回的是对应值的迭代器吗?如果真是这样,那4就不应该被删掉,且和find就没区别了。

事实上,对于lower_bound而言,它返回的是按迭代器遍历顺序大于等于val的值的迭代器,在上面的代码中2存在,于是就把2对应的迭代器返回了回去,如果2不存在就会向上找。

而upper_bound返回的是按迭代器遍历顺序大于val的值的迭代器,在上面的代码中4虽然存在,但它会找比4大的值,返回的是5的迭代器,因此erase按左闭右开的规则会删掉4。

两种迭代器都是向比自己大的值去找,但lower_bound要找等于自己的,upper_bound不找。在erase中却很好理解,s.erase(s.lower_bound(2), s.upper_bound(4));就是删掉2到4之间的所有值(闭区间)。在所有迭代器的组合使用中,都是左闭右开,lower_bound对应左,upper_bound对应右,这样你就明白为什么这样设计了。

如果找不到符合规则的迭代器,那就会返回end。

(6)find

前面我已经介绍了find,这里为什么还要介绍呢?前面的find是set容器里自带的,而这里我想讨论算法库的find和容器里的find的区别

在算法库中,find前两个参数是迭代器区间,第三个是要查找的值

而在set中,不需要前两个参数了。

似乎两者没什么区别,但在底层上区别就很大了。算法库的find只能根据迭代器不断++来找。在set和map中迭代器的顺序就是中序的顺序。但对于set自带的find而言就不是按照中序来找数据了,而是按照平衡二叉搜索树的特点左小右大来找了,在高度次内就能找到。算法库的时间复杂度是O(N),而自带的find时间复杂度是O(logN)

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

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

相关文章

与众不同的社交体验:Facebook的新功能与新变化

在快速变化的社交媒体领域&#xff0c;Facebook不断引入创新功能和变化&#xff0c;以满足用户日益增长的需求&#xff0c;并提供与众不同的社交体验。从增强现实到数据隐私&#xff0c;Facebook的新功能和更新正在塑造一个全新的社交平台。本文将深入探讨这些新功能和变化&…

arm环境下构建Flink的Docker镜像

准备工作 资源准备 按需下载 flink&#xff0c;我的是1.17.2版本。官方说1.13版本之后的安装包兼容了arm架构&#xff0c;所以直接下载就行。 如需要cdc组件&#xff0c;提前下载好。 服务器准备 可在某云上购买arm服务器&#xff0c;2c/4g即可&#xff0c;按量付费。 带宽…

谷粒商城实战笔记-43-前端基础-Vue-使用Vue脚手架进行模块化开发

文章目录 一&#xff0c;Vue的模块化开发1&#xff0c;目录结构2&#xff0c;单文件组件 (SFC)3&#xff0c;模块化路由4&#xff0c;Vuex 模块5&#xff0c;动态组件和异步组件6&#xff0c;抽象和复用7&#xff0c;构建和打包8&#xff0c;测试9&#xff0c;文档和注释10&…

Nginx反向代理概述

正向代理与反向代理概述 正向代理&#xff1a; 定义&#xff1a;正向代理位于客户端和目标服务器之间&#xff0c;客户端的请求首先发送到代理服务器&#xff0c;然后由代理服务器转发到目标服务器&#xff0c;最后将目标服务器的响应返回给客户端。 作用&#xff1a;正向代理…

Linux - 进程的概念、状态、僵尸进程、孤儿进程及进程优先级

目录 进程基本概念 描述进程-PCB task_struct-PCB的一种 task_struct内容分类 查看进程 通过系统目录查看 通过ps命令查看 通过系统调用获取进程的PID和PPID 通过系统调用创建进程- fork初始 fork函数创建子进程 使用if进行分流 Linux进程状态 运行状态-R 浅度睡眠状态-S…

uni-app:踩坑路---关于使用了transform导致fixed定位不生效的问题

前言&#xff1a; 继续记录&#xff0c;在上篇文章中&#xff0c;弹出框遮罩层在ios上没有正确的铺盖全屏&#xff0c;是因为机型的原因&#xff0c;也和我们的代码结构有相关的问题。今天再来展示另外一个奇葩的问题。 这次我使用了在本篇博客中的弹出框组件CustomDialog.vue…

《昇思25天学习打卡营第19天|基于MobileNetv2的垃圾分类》

基于MobileNetv2的垃圾分类 本文档主要介绍垃圾分类代码开发的方法。通过读取本地图像数据作为输入&#xff0c;对图像中的垃圾物体进行检测&#xff0c;并且将检测结果图片保存到文件中。 1、实验目的 了解熟悉垃圾分类应用代码的编写&#xff08;Python语言&#xff09;&a…

【C++】:AVL树的深度解析及其实现

目录 前言一&#xff0c;AVL树的概念二&#xff0c;AVL树节点的定义三&#xff0c;AVL树的插入3.1 第一步3.2 第二步 四&#xff0c;AVL树的旋转4.1 右单旋4.2 左单旋4.3 右左双旋4.4 左右双旋4.5 插入代码的完整实现4.6 旋转总结 五&#xff0c;AVL树的验证六&#xff0c;实现…

插入和选择排序

1.1直接插入排序 void InsertSort(int* a, int n) {for (int i 1; i < n - 1; i) {//i的范围要注意的&#xff0c;防止指针越界int end i;int tmp a[end 1];while (end>0) {if (tmp< a[end]) {a[end 1] a[end];//小于就挪动&#xff0c;虽然会覆盖后面空间的值…

【Linux】通过分配虚拟内存的方式来解决因内存不够而导致部署的项目自动挂掉

多个 jar 包项目部署在同一台服务器上&#xff0c;当服务器配置低&#xff0c;内存不足时&#xff0c;有可能出现 nohup java -jar 启动的进程就莫名其妙挂掉的问题。 解决方式&#xff1a; 第一种方法&#xff1a;进行JVM调优可以改善这种情况&#xff0c;但是项目太多&…

【Android】安卓四大组件之广播知识总结

文章目录 动态注册使用BroadcastReceiver监听Intent广播注册Broadcast Receiver 静态注册自定义广播标准广播发送广播定义广播接收器注册广播接收器 有序广播修改发送方法定义第二个广播接收器注册广播接收器广播截断 使用本地广播实践-强制下线使用ActivityCollector管理所有活…

sql注入 mysql 执行命令 sql注入以及解决的办法

我们以前很可能听过一个词语叫做SQL注入攻击&#xff0c;其是威胁我们系统安全的最危险的因素之一&#xff0c;那么到底什么是SQL注入攻击呢&#xff1f;这里我会用一个最经典最简单的例子来跟大家解释一下&#xff1a; 众所周知&#xff0c;我们的sql语句都是有逻辑的&#xf…

STM32之九:ADC模数转换器

目录 1. 简介 2. ADC 2.1 逐次逼近型寄存器SAR 2.2 ADC转换时间 3 ADC框图 3.1 8 bit ADC0809芯片内部框图 3.2 ADC框图 3.2.1 注入通道和规则通道 3.2.2 单次/连续转换模式 3.2.3 扫描模式 3.2.4 外部触发转换 3.2.5 数据对齐 3.2.6 模拟看门狗 4. 总结和ADC驱…

MYSQL ODBC驱动安装时的注意事项

今天想使用MYSQL的ODBC驱动连接数据库。 安装的时候遇到一个大坑&#xff0c;在这里记录一下。 window 64位的操作&#xff0c;要安装64位驱动&#xff0c;这个大家都知道了。 有以下的问题要注意区别的。 1 、windows是64位的&#xff0c;但是开发软件是32位的。 这个时候…

OpenStack Yoga版安装笔记(七)glance练习补充

1、练习场景说明 在OpenStack Yoga版安装笔记&#xff08;五&#xff09;中&#xff0c;glance已经在controller node虚拟机上安装完成&#xff0c;并且已经成功拍摄了快照。 此时&#xff0c;controller node虚机已经安装了keystone、keystone DB、glance、glance DB、OpenSta…

PCL-基于FPFH的SAC-IA结合ICP的点云配准方法

目录 一、相关方法原理1.凸包方法2.FPFH特征描述3.SAC-IA概述4.ICP概述 二、实验代码三、实验结果 一、相关方法原理 点云是在同一空间参考系下表达目标空间分布和目标表面特性的海量点集合&#xff0c;在获取物体表面每个采样点的空间坐标后&#xff0c;得到的是点的集合&…

构建智能运维系统:创新架构与效率优化

随着信息技术的迅猛发展&#xff0c;企业对于运维效率和服务质量的要求越来越高。智能运维系统的设计和实施&#xff0c;不仅能够提升系统可靠性和响应速度&#xff0c;还能有效降低成本和人力投入。本文将深入探讨智能运维系统的架构设计原则和关键技术&#xff0c;为企业在运…

数据结构重置版(概念篇)

本篇文章是对数据结构的重置&#xff0c;且只涉及概念 顺序表与链表的区别 不同点 顺序表 链表 存储空间上 物理上一定连续 逻辑上连续&#xff0c;但物理上不一定连续…

.env.local 配置本地环境变量 用于团队开发

.env.local 用途&#xff1a;.env.local 通常用于存储本地开发环境中的环境变量。这些变量可能包括敏感数据或特定于单个开发者的设置&#xff0c;不应该被提交到版本控制系统中。优先级&#xff1a;在大多数框架中&#xff0c;.env.local 文件中的变量会覆盖其他 .env 文件中…

分类模型的完整流程及Python实现

1、加载函数和数据集 import numpy as np from sklearn.datasets import load_breast_cancer from sklearn.svm import SVC from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler import matplotlib.pyplot as plt cancer…