【STL】 set 与 multiset:基础、操作与应用

在 C++ 标准库中,set 和 multiset 是两个非常常见的关联容器,主要用于存储和管理具有一定规则的数据集合。本文将详细讲解如何使用这两个容器,并结合实例代码,分析其操作和特性。

0.基础操作概览

0.1.构造:

set<T> st;                   
// 默认构造函数:set(const set& st);          
//拷贝构造函数

0.2.赋值:

set& operator=(const set& st); 
//重载等号操作符

0.3.统计set容器大小以及交换set容器

size();                       
//返回容器中元素的数目empty();                       
//判断容器是否为空swap(st);                     //交换两个集合容器

0.4.set容器进行插入数据和删除数据

insert(elem);                  
//在容器中插入元素。      clear();                       
// 清除所有元素
erase(pos);                         
//删除pos迭代器所指的元素,返回下一个元素的迭代器。erase(beg, end);                   
//删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。erase(elem);                       
//删除容器中值为elem的元素。

0.5.set查找和统计:对set容器进行查找数据以及统计数据

find(key);          
//查找key是否存在,若存在,返回该键的元素的迭代器;
//若不存在,返回set.end();count(key);                        
//对于set而言,统计key的元素个数
//只有两种结果:如果容器中不存在key,返回0; 否则,返回1

0.6.排序

set<int> st1;               
// 储存int的集合(从小到大)set<int, greater<int>> st2; 
// 储存int的集合(从大到小)

1. set 与 multiset 的基本概念

  • set:它是一种自动去重且按顺序排列的集合。每次插入元素时,set 会自动判断该元素是否已存在,若存在则不会插入。
  • multiset:允许重复元素的集合,因此可以存储多个相同的元素。
  • 关联容器:关联容器中的元素在插入时自动排序,因此不同于顺序容器如 vector 或 list,不需要手动排序。

2. set 容器的构造与赋值

在 C++ 中,set 提供了多种构造方式:

  • 默认构造set <int> st; 创建一个空的整数集合。
  • 拷贝构造set<int> st2(st); 从已有的集合 st 创建一个副本。
  • 赋值操作set& operator=(const set& st);可以通过重载的 = 操作符将一个集合的内容赋值给另一个集合。

构造与赋值实例:

void test0()
{// 1.默认构造set<int>s;s.insert(1);  // 插入元素1s.insert(-4); // 插入元素-4s.insert(2);  // 插入元素2s.insert(5);  // 插入元素5s.insert(8);  // 插入元素8s.insert(1);  // 插入重复元素1,自动忽略print(s);  // 打印集合中的元素,输出为:-4 1 2 5 8cout << endl;// 2.拷贝构造set<int>s2(s);  // 拷贝构造函数print(s2);  // 输出:-4 1 2 5 8cout << endl;// 3.赋值操作set<int>s3 = s2;  // 赋值操作符print(s3);  // 输出:-4 1 2 5 8
}

在这里插入图片描述

在此代码中,set 的插入操作可以看出,重复元素(如插入的 1)不会出现在集合中,这是 set 自动去重的特性。

3.遍历 set 容器

在 set 中,不能使用下标访问元素,因此遍历集合有两种常见方式:

  • 使用迭代器遍历:通过迭代器访问集合元素,适合所有关联容器。
  • 基于范围的 for 循环:简化迭代器操作,代码更加简洁。

迭代器遍历:

void print(set<int>& s){for (set<int>::iterator it = s.begin(); it != s.end(); it++) {cout << *it << " ";  // 输出每个元素}cout << endl;/*for (auto it = s.begin(); it != s.end(); it++){cout << *it << " ";}*/
}

基于范围的 for 循环

void print(set<int>& s) 
{for (int elem : s) {cout << elem << " ";  // 直接输出元素}cout << endl;/*for (auto it : s){cout << it << " ";}*/
}

降序遍历:如果集合是降序排序的,例如使用
set<int, greater>,需要在定义迭代器时添加相应的比较器。

void print(set<int, greater<int>>& s) 
{for (set<int, greater<int>>::iterator it = s.begin(); it != s.end(); it++) {cout << *it << " ";  // 输出降序排列的元素}cout << endl;
}

遍历操作

void test_traversal() 
{set<int> s;s.insert(5);s.insert(1);s.insert(3);s.insert(7);cout << "使用迭代器遍历 set:" << endl;print(s);  // 输出:1 3 5 7cout << "使用基于范围的 for 循环遍历 set:" << endl;print2(s);  // 输出:1 3 5 7
}

总结:遍历 set 的两种方式都非常直观,迭代器方式适合所有关联容器,而基于范围的 for 循环则能让代码更简洁。

5. set 容器的大小统计与交换

  • size();返回集合中的元素数量。
  • empty();检查集合是否为空,若为空返回 true,否则返回false。
  • swap();交换两个集合的内容。

统计大小与交换

void test1()
{//构造2个有数据的set容器set<int>s;s.insert(0);s.insert(1);s.insert(0);s.insert(9);s.insert(6);s.insert(11);set<int>s1;s1.insert(0);s1.insert(0);s1.insert(0);s1.insert(99);s1.insert(4);s1.insert(-1);cout << endl;//判断是否为空if (!s.empty()) {cout << "s容器的长度为:" << s.size() << endl;}cout << endl;//交换cout<< "交换前:" << endl;cout << "s: " ;print(s);cout << endl << "s1:";print(s1);s.swap(s1);cout << endl << endl;cout << "交换后:" << endl ;cout << "s: " ;print(s);cout << endl << "s1: ";print(s1);
}

在这里插入图片描述

通过 swap() 操作,s 和 s1 的内容得到了交换。这样可以有效简化代码,避免使用临时变量来保存集合的内容。

6. set 容器的插入与删除操作

  • insert(elem);向集合中插入元素。由于 set 自动排序,插入元素时不会指定位置。

删除元素有三种方式:

  • erase(pos); 删除pos迭代器所指的元素,返回下一个元素的迭代器。

  • erase(beg, end); 删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。

  • erase(elem); 删除容器中值为elem的元素。

void test2()
{set<int>s1;s1.insert(100);s1.insert(100);s1.insert(20);s1.insert(0);s1.insert(44);s1.insert(12);s1.insert(666);s1.insert(9);print(s1);cout << endl;//删除迭代器指定元素s1.erase(s1.begin());//这里删掉的是第一个元素0,因为set容器自动排序print(s1);cout << endl;//删除指定元素s1.erase(20);print(s1);cout << endl;//删除迭代器指定区间元素s1.erase(s1.begin(), s1.end());//等价于清空操作://s1.clear();cout <<"当前s1的大小为:" << s1.size() << endl;
}

在这里插入图片描述

在此代码中,我们展示了三种不同的 erase 操作,可以方便地删除单个元素或一组元素。

7. set 容器的查找与统计

find(key);
查找key是否存在,若存在,返回该键的元素的迭代器;
若不存在,返回set.end();

count(key);
对于set而言,统计key的元素个数
只有两种结果:如果容器中不存在key,返回0; 否则,返回1

查找与统计:

void test3(){set<int>s1;s1.insert(1);s1.insert(100);s1.insert(23);s1.insert(0);s1.insert(404);s1.insert(12);s1.insert(999);s1.insert(9);// 查找元素 404auto it = s1.find(404);if (it != s1.end()) {cout << "找到了元素 404" << endl;  // 输出:找到了元素404} else {cout << "未找到元素 404" << endl;}// 使用 count 统计元素个数int n = s1.count(404);cout << "s1 中元素 404 的个数为:" << n << endl;  // 输出:1
}

通过 find() 和 count() 函数,可以轻松实现元素的查找与存在性检查。

8. set 容器的排序特性

默认情况下,set 按照升序排列。可以通过
set<int, greater<int>> 指定降序排列。

  • set<int> st1; 储存int的集合(从小到大)
  • set<int, greater<int>> st2; 储存int的集合(从大到小)
    排序:
void test4()
{//默认构造set<int>s1;//等价于set<int, less<int>>s1;s1.insert(10);s1.insert(40);s1.insert(20);s1.insert(0);s1.insert(-10);print(s1);//降序构造,用到比较器greater(类型)cout << endl;set<int, greater<int>>s2;s2.insert(10);s2.insert(40);s2.insert(20);s2.insert(0);s2.insert(-10);print2(s2);
}

通过比较器 greater 可以实现集合的降序排列。

整体操作如下:

//set:自动去重 且 按顺序排列的 集合
//multiset:允许重复元素
//关联容器:插入时自动排序
//只能使用迭代器,基于范围的for循环 进行遍历,不能使用下标访问
#include<iostream>
#include<set>
using namespace std;//1.构造:
//set<T> st;                         // 默认构造函数:
//set(const set& st);                //拷贝构造函数//2.赋值:
//set& operator=(const set& st);     //重载等号操作符//3.统计set容器大小以及交换set容器
//size();                            //返回容器中元素的数目
//empty();                           //判断容器是否为空
//swap(st);                          //交换两个集合容器//4.set容器进行插入数据和删除数据
//insert(elem);                      //在容器中插入元素。      
//clear();                           // 清除所有元素//erase(pos);                        //删除pos迭代器所指的元素,返回下一个元素的迭代器。
//erase(beg, end);                   //删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
//erase(elem);                       //删除容器中值为elem的元素。//5.set查找和统计:对set容器进行查找数据以及统计数据
//find(key);                         //查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end();
//count(key);                        //对于set而言,统计key的元素个数:只有两种结果:0和1,如果容器中不存在key,返回0; 否则,返回1//6.排序
//set<int> st1;               // 储存int的集合(从小到大)
//set<int, greater<int>> st2; // 储存int的集合(从大到小)//遍历set容器(用迭代器,基于范围for循环)
void print(set<int>& s)
{for (set<int>::iterator it = s.begin(); it != s.end(); it++){cout << *it << " ";}/*for (auto it = s.begin(); it != s.end(); it++){cout << *it << " ";}*//*for (auto it : s){cout << it << " ";}*/}//遍历降序set容器
//注意:如果是降序set容器,在传参,定义迭代器时,都要加上比较器:greater<int>
void print2(set<int,greater<int>>& s)
{for (set<int,greater<int>>::iterator it = s.begin(); it != s.end(); it++){cout << *it << " ";}/*for (auto it = s.begin(); it != s.end(); it++){cout << *it << " ";}*//*for (auto it : s){cout << it << " ";}*/
}//1.构造与赋值
void test0()
{// 1.默认构造set<int>s;s.insert(1);  // 插入元素1s.insert(-4); // 插入元素-4s.insert(2);  // 插入元素2s.insert(5);  // 插入元素5s.insert(8);  // 插入元素8s.insert(1);  // 插入重复元素1,自动忽略print(s);  // 打印集合中的元素,输出为:-4 1 2 5 8cout << endl;// 2.拷贝构造set<int>s2(s);  // 拷贝构造函数print(s2);  // 输出:-4 1 2 5 8cout << endl;// 3.赋值操作set<int>s3 = s2;  // 赋值操作符print(s3);  // 输出:-4 1 2 5 8
}//2.统计set容器大小以及交换set容器
void test1()
{//构造2个有数据的set容器set<int>s;s.insert(0);s.insert(1);s.insert(0);s.insert(9);s.insert(6);s.insert(11);set<int>s1;s1.insert(0);s1.insert(0);s1.insert(0);s1.insert(99);s1.insert(4);s1.insert(-1);//判断是否为空if (!s.empty()) {cout << "s容器的长度为:" << s.size() << endl;}cout << endl;//交换cout<< "交换前:" << endl;cout << "s: " ;print(s);cout << endl << "s1:";print(s1);s.swap(s1);cout << endl << endl;cout << "交换后:" << endl ;cout << "s: " ;print(s);cout << endl << "s1: ";print(s1);
}//3.set容器进行插入数据和删除数据
void test2()
{set<int>s1;s1.insert(100);s1.insert(100);s1.insert(20);s1.insert(0);s1.insert(44);s1.insert(12);s1.insert(666);s1.insert(9);print(s1);cout << endl;//删除迭代器指定元素s1.erase(s1.begin());//这里删掉的是第一个元素0,因为set容器自动排序print(s1);cout << endl;//删除指定元素s1.erase(20);print(s1);cout << endl;//删除迭代器指定区间元素s1.erase(s1.begin(), s1.end());//等价于清空操作://s1.clear();cout <<"当前s1的大小为:" << s1.size() << endl;
}//4.set查找和统计:对set容器进行查找数据以及统计数据
void test3()
{set<int>s1;s1.insert(1);s1.insert(100);s1.insert(23);s1.insert(0);s1.insert(404);s1.insert(12);s1.insert(999);s1.insert(9);//查找:返回迭代器//set<int>::iterator it = s1.find(404);//用auto更方便auto it = s1.find(404);if (it != s1.end()){cout << "找到了" << endl;}else{cout << "没找到" << endl;}//统计个数,也可以用来查找int n = s1.count(404);if (n){cout << "找到了" << endl;}else{cout << "没找到" << endl;}cout <<"s1中404的个数为:" << n << endl;
}//5.set容器的指定排序:默认从小到大排序
void test4()
{//默认构造set<int>s1;//等价于set<int, less<int>>s1;s1.insert(10);s1.insert(40);s1.insert(20);s1.insert(0);s1.insert(-10);print(s1);//降序构造,用到比较器greater(类型)cout << endl;set<int, greater<int>>s2;s2.insert(10);s2.insert(40);s2.insert(20);s2.insert(0);s2.insert(-10);print2(s2);}int main()
{test0();test1();test2();test3();test4();return 0;
}

9.相关注意事项

9.1. 自动排序与元素唯一性

自动排序:set 会自动将插入的元素按升序(或根据提供的自定义比较器排序)进行排序。因此,插入顺序与实际存储顺序可能不同。

注意:由于 set 是自动排序的容器,插入操作可能会引发排序操作,这使得插入操作的平均时间复杂度为 O(log n),适合需要快速查找和去重的场景。

元素唯一性:set 不允许重复元素。如果插入的元素已经存在于集合中,set 会自动忽略该元素。即使多次插入相同的值,集合中只会保留一个副本。

set<int> s;
s.insert(1);
s.insert(1);  // 插入重复元素,set 会自动忽略
print(s);     // 输出:1

9.2. 不能使用下标访问

与 vector 不同,set 作为关联容器不能通过下标访问元素,也不能像顺序容器那样直接修改元素。只能通过迭代器或基于范围的 for 循环来遍历集合。

set<int> s = {10, 20, 30};
// s[0] = 5;  // 错误!set 不能使用下标
for (int elem : s) 
{cout << elem << " ";  // 正确的遍历方式
}

9.3. 修改元素的限制

在 set 中,由于其自动排序的特性,不能直接通过迭代器修改元素的值如果需要修改元素值,必须先删除该元素,然后插入新的值。

set<int> s = {10, 20, 30};
auto it = s.find(20);
if (it != s.end()) 
{s.erase(it);     // 先删除 20s.insert(25);    // 再插入新值 25
}
print(s);  // 输出:10 25 30

9.4. 迭代器的有效性

当删除或插入元素时,set 的迭代器可能会失效。特别是在删除操作中,如果需要使用迭代器操作,建议在删除之后重新获取下一个有效的迭代器。

set<int> s = {1, 2, 3, 4, 5};
auto it = s.begin();
while (it != s.end()) 
{if (*it == 3) {it = s.erase(it);  // erase 返回下一个有效迭代器} else {it++;}
}
print(s);  // 输出:1 2 4 5

9.6. 自定义排序规则

如果需要按自定义顺序排序 set 中的元素,可以通过传入自定义的比较器来改变排序方式。例如,可以使用 greater 来实现降序排列,或者提供自定义的比较函数。

set<int, greater<int>> s = {10, 20, 30};
print2(s);  // 输出:30 20 10

9.8. 避免重复调用 find 和 count

如果你想要同时查找元素和统计某个元素的出现次数,不必重复调用 find() 和 count(),因为 count() 可以直接返回是否存在目标元素(对于 set,返回值只会是 0 或 1)。count() 本质上相当于 find() 的简化版。

set<int> s = {10, 20, 30};
if (s.count(20)) 
{cout << "元素 20 存在" << endl;
}

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

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

相关文章

深度学习:神经网络--手写数字识别

目录 一、datasets 1.datasets简介 2.主要特点 二、MNIST 三、使用神经网络实现手写数字识别 1.创建数据加载器 2.判断是否使用GPU 3.创建神经网络 4.创建训练集模型 5.创建测试集模型 6.创建损失函数和优化器并训练 一、datasets 1.datasets简介 datasets是一个广…

[mysql]mysql排序和分页

#排序和分页本身是两块内容,因为都比较简单,我们就把它分到通一个内容里. #1排序: SELECT * FROM employees #我们会发现,我们没有做排序操作,但是最后出来的107条结果还是会按顺序发出,而且是每次都一样.这我们就有一个疑惑了,现在我们的数据库是根据什么来排序的,在我们没有进…

windows 驱动实例分析系列-COM驱动案例讲解

COM也被称之为串口,这是一种非常简单的通讯接口,这种结构简单的接口被广泛的应用在开发中,几乎所有系统都能支持这种通讯接口,它有RS232和RS485等分支,但一般我们都会使用RS232作为常见的串口,因为它足够简单和高效。 几乎所有的开发板,都会提供用于烧录、调试、日志的…

redis为什么不使用一致性hash

Redis节点间通信时&#xff0c;心跳包会携带节点的所有槽信息&#xff0c;它能以幂等方式来更新配置。如果采用 16384 个插槽&#xff0c;占空间 2KB (16384/8);如果采用 65536 个插槽&#xff0c;占空间 8KB (65536/8)。 今天我们聊个知识点为什么Redis使用哈希槽而不是一致性…

FastAPI 的隐藏宝石:自动生成 TypeScript 客户端

在现代 Web 开发中&#xff0c;前后端分离已成为标准做法。这种架构允许前端和后端独立开发和扩展&#xff0c;但同时也带来了如何高效交互的问题。FastAPI&#xff0c;作为一个新兴的 Python Web 框架&#xff0c;提供了一个优雅的解决方案&#xff1a;自动生成客户端代码。本…

引领长期投资新篇章:价值增长与财务安全的双重保障

随着全球金融市场的不断演变&#xff0c;长期投资策略因其稳健性和对价值增长的显著推动作用而日益受到投资者的重视。在这一背景下&#xff0c;Zeal Digital Shares&#xff08;ZDS&#xff09;项目以其创新的数字股票产品&#xff0c;为全球投资者提供了一个全新的长期投资平…

re题(38)BUUCTF-[FlareOn6]Overlong

BUUCTF在线评测 (buuoj.cn) 运行一下.exe文件 查壳是32位的文件&#xff0c;放到ida反汇编 对unk_402008前28位进行一个操作&#xff0c;我们看到运行.exe文件的窗口正好是28个字符&#xff0c;而unk_402008中不止28个数据&#xff0c;所以猜测MessageBoxA&#xff08;&#x…

cv中每个patch的关联

在计算机视觉任务中&#xff0c;当图像被划分为多个小块&#xff08;patches&#xff09;时&#xff0c;每个 patch 的关联性可以通过不同的方法来计算。具体取决于使用的模型和任务&#xff0c;以下是一些常见的计算 patch 关联性的方法&#xff1a; 1. Vision Transformer (…

Shell运行原理与Linux权限概念

shell的运行原理 Linux严格意义上说的是一个操作系统。我们称之为“核心&#xff08;kernel&#xff09;”&#xff0c;但我们一般用户&#xff0c;不能直接使用kernel&#xff0c;二十通过kernel的“外壳”程序&#xff0c;也就是所谓的shell&#xff0c;来与kernel沟通。 从…

网络穿透:TCP 打洞、UDP 打洞与 UPnP

在现代网络中&#xff0c;很多设备都处于 NAT&#xff08;网络地址转换&#xff09;或防火墙后面&#xff0c;这使得直接访问这些设备变得困难。在这种情况下&#xff0c;网络穿透技术就显得非常重要。本文将介绍三种常用的网络穿透技术&#xff1a;TCP 打洞、UDP 打洞和 UPnP。…

qt-C++笔记之作用等同的宏和关键字

qt-C笔记之作用等同的宏和关键字 code review! Q_SLOT 和 slots&#xff1a; Q_SLOT是slots的替代宏&#xff0c;用于声明槽函数。 Q_SIGNAL 和 signals&#xff1a; Q_SIGNAL类似于signals&#xff0c;用于声明信号。 Q_EMIT 和 emit&#xff1a; Q_EMIT 是 Qt 中用于发射…

18.2K Star,AI 高效视频监控摄像头

Hi&#xff0c;骚年&#xff0c;我是大 G&#xff0c;公众号「GitHub 指北」会推荐 GitHub 上有趣有用的项目&#xff0c;一分钟 get 一个优秀的开源项目&#xff0c;挖掘开源的价值&#xff0c;欢迎关注。 导语 在家庭和企业安防领域&#xff0c;实时视频监控是保障安全的核…

AIGC8: 高通骁龙AIPC开发者大会记录B

图中是一个小男孩在市场卖他的作品。 AI应用开发出来之后&#xff0c;无论是个人开发者还是企业开发者。 如何推广分发是面临的大问题。 做出来的东西一定要符合商业规律。否则就是实验室里面的玩物&#xff0c;或者自嗨的东西。 背景 上次是回顾和思考前面两个硬件营销总的…

【JVM】类加载

1. 类加载过程 Java虚拟机&#xff08;JVM&#xff09;的 类加载 过程是将字节码文件&#xff08;.class文件&#xff09;从存储设备加载到内存&#xff0c;并为其创建相应的类对象的过程。类加载是Java程序运行的基础&#xff0c;保证了程序的动态性和安全性。JVM的类加载过程…

人工智能 | 基于ChatGPT开发人工智能服务平台

简介 ChatGPT 在刚问世的时候&#xff0c;其产品形态就是一个问答机器人。而基于ChatGPT的能力还可以对其做一些二次开发和拓展。比如模拟面试功能、或者智能机器人功能。 模拟面试功能包括个性化问题生成、实时反馈、多轮面试模拟、面试报告。 智能机器人功能提供24/7客服支…

将阮一峰老师的《ES6入门教程》的源码拷贝本地运行和发布

你好同学&#xff0c;我是沐爸&#xff0c;欢迎点赞、收藏、评论和关注。 阮一峰老师的《ES6入门教程》应该是很多同学学习 ES6 知识的重要参考吧&#xff0c;应该也有很多同学在看该文档的时候&#xff0c;想知道这个教程的前端源码是怎么实现的&#xff0c;也可能有同学下载…

esp32 wifi 联网后,用http 发送hello 用pc 浏览器查看网页

参考chatgpt Esp32可以配置为http服务器&#xff0c;可以socket编程。为了免除编写针对各种操作系统的app。完全可以用浏览器仿问esp32服务器&#xff0c;获取esp32的各种数据&#xff0c;甚至esp的音频&#xff0c;视频。也可以利用浏览器对esp进行各种操作。但esp不能主动仿…

【医学半监督】置信度指导遮蔽学习的半监督医学图像分割

摘要: 半监督学习(Semi-supervised learning)旨在利用少数标记数据和多数未标记数据训练出高性能模型。现有方法大多采用预测任务机制,在一致性或伪标签的约束下获得精确的分割图,但该机制通常无法克服确认偏差。针对这一问题,本文提出了一种用于半监督医学图像分割的新…

【C++笔记】C++编译器拷贝优化和内存管理

【C笔记】C编译器拷贝优化和内存管理 &#x1f525;个人主页&#xff1a;大白的编程日记 &#x1f525;专栏&#xff1a;C笔记 文章目录 【C笔记】C编译器拷贝优化和内存管理前言一.对象拷贝时的编译器优化二.C/C内存管理2.1练习2.2 C内存管理方式2.3 operator new与operator…

分布式锁优化之 使用lua脚本改造分布式锁保证判断和删除的原子性(优化之LUA脚本保证删除的原子性)

文章目录 1、lua脚本入门1.1、变量&#xff1a;弱类型1.2、流程控制1.3、在lua中执行redis指令1.4、实战&#xff1a;先判断是否自己的锁&#xff0c;如果是才能删除 2、AlbumInfoApiController --》testLock()3、AlbumInfoServiceImpl --》testLock() 1、lua脚本入门 Lua 教程…