【C++进阶】关联容器:set类型

目录

一、set 基本概念

1.1 定义与特点

1.2 头文件与声明

1.3 核心特性解析

二、set 底层实现

2.1 红黑树简介

2.2 红黑树在 set 中的应用

三、set 常用操作

3.1 插入元素

3.2 删除元素

3.3 查找元素

3.4 遍历元素

3.5 性能特征

四、set 高级应用

4.1 自定义比较函数

4.2 交集、并集和差集操作

4.3 与其他容器的结合使用

4.4 迭代器操作

4.5 性能优化技巧

五、set 与其他容器的比较

5.1 set 与 vector

5.2 set 与 unordered_set

5.3 选择建议

六、注意事项与常见错误

6.1 迭代器失效问题

6.2 性能考虑

6.3 内存占用

6.4 易错点提醒

七、应用场景与实战案例

八、总结

九、参考资料


在 C++ 编程中,容器是非常重要的工具,它可以帮助我们高效地管理和操作数据。关联容器是 C++ 标准模板库(STL)中的一类特殊容器,它们通过键(key)来存储和访问元素。set 作为关联容器的一种,在很多场景下都有着广泛的应用。本文将全面深入地介绍 set 类型,包括其基本概念、底层实现、常用操作、高级应用以及与其他容器的比较等内容。

一、set 基本概念

1.1 定义与特点

set 是一种关联容器,它用于存储唯一的元素,并且这些元素会根据其值自动进行排序。在 set 中,键(key)和值(value)是相同的,即每个元素既是键也是值。由于 set 不允许有重复的元素,因此插入重复元素时会被忽略。

1.2 头文件与声明

要使用 set,需要包含 <set> 头文件。以下是一个简单的 set 声明示例:

#include <iostream>
#include <set>int main() {std::set<int> mySet; // 声明一个存储 int 类型元素的 setreturn 0;
}

1.3 核心特性解析

std::set是基于红黑树实现的有序关联容器,其设计目标是通过平衡二叉搜索树结构,在保证元素唯一性的同时,实现以下核心特性:

①自动排序机制

  • 插入元素时自动按升序排列
  • 默认使用<运算符比较元素
  • 支持自定义比较函数

②唯一性约束

  • 不允许重复元素
  • 插入重复值时自动去重

③对数时间复杂度操作

  • 插入:O(log n)
  • 删除:O(log n)
  • 查找:O(log n)

④内存动态管理

  • 自动处理内存分配/释放
  • 支持迭代器失效保护

二、set 底层实现

2.1 红黑树简介

set 通常使用红黑树(Red-Black Tree)作为底层数据结构。红黑树是一种自平衡的二叉搜索树,它通过对节点进行着色(红色或黑色)并遵循一些特定的规则来保持树的平衡,从而保证了插入、删除和查找操作的时间复杂度都是 O(logn)。

红黑树的主要特性包括:

  • 每个节点要么是红色,要么是黑色。
  • 根节点是黑色。
  • 每个叶子节点(NIL 节点,空节点)是黑色。
  • 如果一个节点是红色的,则它的两个子节点都是黑色的。
  • 对每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。

2.2 红黑树在 set 中的应用

在 set 中,红黑树的节点存储了 set 中的元素。当插入一个新元素时,set 会根据元素的值将其插入到红黑树的合适位置,并通过旋转和着色操作来保持树的平衡。查找元素时,set 会从根节点开始,根据元素的值与节点的值进行比较,然后递归地在左子树或右子树中查找,直到找到目标元素或到达叶子节点。

三、set 常用操作

3.1 插入元素

可以使用 insert() 函数向 set 中插入元素。如果插入的元素已经存在于 set 中,则插入操作会被忽略。

#include <iostream>
#include <set>int main() {std::set<int> mySet;mySet.insert(3);mySet.insert(1);mySet.insert(2);for (const auto& element : mySet) {std::cout << element << " ";}std::cout << std::endl;return 0;
}

输出:1 2 3,因为 set 会自动对元素进行排序。

3.2 删除元素

可以使用 erase() 函数从 set 中删除元素。erase() 函数有多种重载形式,可以根据元素的值或迭代器来删除元素。

#include <iostream>
#include <set>int main() {std::set<int> mySet = {1, 2, 3, 4, 5};mySet.erase(3); // 根据元素的值删除auto it = mySet.find(4);if (it != mySet.end()) {mySet.erase(it); // 根据迭代器删除}for (const auto& element : mySet) {std::cout << element << " ";}std::cout << std::endl;return 0;
}

3.3 查找元素

可以使用 find() 函数在 set 中查找元素。如果找到元素,则返回指向该元素的迭代器;如果未找到,则返回 end() 迭代器。

#include <iostream>
#include <set>int main() {std::set<int> mySet = {1, 2, 3, 4, 5};auto it = mySet.find(3);if (it != mySet.end()) {std::cout << "Found: " << *it << std::endl;} else {std::cout << "Not found" << std::endl;}return 0;
}

3.4 遍历元素

可以使用范围 for 循环或迭代器来遍历 set 中的元素。

#include <iostream>
#include <set>int main() {std::set<int> mySet = {1, 2, 3, 4, 5};// 使用范围 for 循环遍历for (const auto& element : mySet) {std::cout << element << " ";}std::cout << std::endl;// 使用迭代器遍历for (auto it = mySet.begin(); it != mySet.end(); ++it) {std::cout << *it << " ";}std::cout << std::endl;return 0;
}

3.5 性能特征

操作时间复杂度说明
insert()O(log n)插入新元素
erase()O(log n)删除元素
find()O(log n)查找元素
count()O(log n)统计元素出现次数
size()O(1)获取元素数量
empty()O(1)检查是否为空

四、set 高级应用

4.1 自定义比较函数

默认情况下,set 使用 std::less 作为比较函数来对元素进行排序。如果需要自定义排序规则,可以在声明 set 时提供自定义的比较函数。

#include <iostream>
#include <set>// 自定义比较函数,实现降序排序
struct Greater {bool operator()(const int& a, const int& b) const {return a > b;}
};int main() {std::set<int, Greater> mySet = {1, 2, 3, 4, 5};for (const auto& element : mySet) {std::cout << element << " ";}std::cout << std::endl;return 0;
}

4.2 交集、并集和差集操作

可以使用 <algorithm> 头文件中的 set_intersection()set_union() 和 set_difference() 函数来实现 set 的交集、并集和差集操作。

#include <iostream>
#include <set>
#include <algorithm>
#include <vector>int main() {std::set<int> set1 = {1, 2, 3, 4, 5};std::set<int> set2 = {3, 4, 5, 6, 7};std::vector<int> intersection;std::set_intersection(set1.begin(), set1.end(), set2.begin(), set2.end(), std::back_inserter(intersection));std::cout << "Intersection: ";for (const auto& element : intersection) {std::cout << element << " ";}std::cout << std::endl;std::vector<int> unionSet;std::set_union(set1.begin(), set1.end(), set2.begin(), set2.end(), std::back_inserter(unionSet));std::cout << "Union: ";for (const auto& element : unionSet) {std::cout << element << " ";}std::cout << std::endl;std::vector<int> difference;std::set_difference(set1.begin(), set1.end(), set2.begin(), set2.end(), std::back_inserter(difference));std::cout << "Difference (set1 - set2): ";for (const auto& element : difference) {std::cout << element << " ";}std::cout << std::endl;return 0;
}

4.3 与其他容器的结合使用

set 可以与其他容器(如 vectorlist 等)结合使用,以实现更复杂的功能。例如,可以将 vector 中的元素插入到 set 中进行去重和排序。

#include <iostream>
#include <set>
#include <vector>int main() {std::vector<int> vec = {3, 1, 2, 3, 4, 2};std::set<int> mySet(vec.begin(), vec.end());for (const auto& element : mySet) {std::cout << element << " ";}std::cout << std::endl;return 0;
}

4.4 迭代器操作

std::set<int> s = {1, 3, 5, 7, 9};// 反向迭代器
std::cout << "反向遍历: ";
for (auto rit = s.rbegin(); rit != s.rend(); ++rit) {std::cout << *rit << " ";
}// 迭代器失效处理
auto it = s.find(5);
s.erase(it); // 迭代器失效,不可继续使用

4.5 性能优化技巧

批量插入优化:

std::set<int> s;
s.insert({1, 2, 3, 4, 5}); // C++11初始化列表优化

emplace原位构造:

s.emplace(10); // 直接在容器内构造对象,避免临时对象拷贝

预留空间(C++11起): 

s.reserve(100); // 预分配内存减少重分配次数

五、set 与其他容器的比较

操作setunordered_setvector有序
插入O(log n)O(1)平均O(n)
查找O(log n)O(1)平均O(log n)二分
删除O(log n)O(1)平均O(n)
内存占用较高较低紧凑
有序性始终有序无序需排序

5.1 set 与 vector

  • 存储方式vector 是一种顺序容器,它使用连续的内存空间存储元素;而 set 是一种关联容器,使用红黑树存储元素。
  • 元素特性vector 允许有重复的元素,并且元素的顺序是按照插入的顺序排列的;set 不允许有重复的元素,并且元素会自动排序。
  • 查找效率vector 的查找操作的时间复杂度为 O(n),而 set 的查找操作的时间复杂度为 O(logn)。

5.2 set 与 unordered_set

  • 底层实现set 使用红黑树作为底层数据结构,而 unordered_set 使用哈希表作为底层数据结构。
  • 排序特性set 中的元素会自动排序,而 unordered_set 中的元素是无序的。
  • 查找效率unordered_set 的查找操作的平均时间复杂度为 O(1),而 set 的查找操作的时间复杂度为 O(logn)。

5.3 选择建议

  • 如果需要存储唯一的元素并且要求元素有序,那么可以选择 set
  • 如果只需要存储唯一的元素,不关心元素的顺序,并且对查找效率有较高的要求,那么可以选择 unordered_set
  • 如果需要存储多个相同的元素,并且对元素的顺序有要求,那么可以选择 vector

六、注意事项与常见错误

6.1 迭代器失效问题

在对 set 进行插入或删除操作时,可能会导致迭代器失效。例如,在删除一个元素后,指向该元素的迭代器将失效。因此,在使用迭代器时,需要特别注意避免迭代器失效的问题。

#include <iostream>
#include <set>int main() {std::set<int> mySet = {1, 2, 3, 4, 5};auto it = mySet.find(3);if (it != mySet.end()) {mySet.erase(it); // 删除元素后,it 迭代器失效// 不能再使用 it 迭代器}return 0;
}

6.2 性能考虑

虽然 set 的插入、删除和查找操作的时间复杂度都是 O(logn),但在某些情况下,可能会有性能瓶颈。例如,当需要频繁插入和删除元素时,红黑树的旋转和着色操作可能会影响性能。此时,可以考虑使用 unordered_set 来提高性能。

6.3 内存占用

由于 set 使用红黑树作为底层数据结构,它需要额外的内存来存储节点的指针和颜色信息。因此,在对内存占用有严格要求的场景下,需要谨慎使用 set

6.4 易错点提醒

修改元素值:直接修改元素会导致未定义行为

auto it = s.begin();
// *it = 10;  // 错误!元素是const的

迭代器失效:仅删除操作会使指向被删元素的迭代器失效

自定义比较函数:需保证严格弱序关系

// 错误示例:未实现严格弱序
struct BadCompare {bool operator()(int a, int b) {return a <= b;  // 应该用<}
};

七、应用场景与实战案例

案例1:数据去重排序

std::vector<int> nums = {5, 2, 5, 1, 3, 2};
std::set<int> unique_nums(nums.begin(), nums.end());// 输出:1 2 3 5
for(int num : unique_nums) {std::cout << num << " ";
}

案例2:字典序管理

std::set<std::string> dictionary;void add_word(const std::string& word) {dictionary.insert(word);
}bool check_spelling(const std::string& word) {return dictionary.count(word);
}

八、总结

set作为STL中的有序唯一集合容器,在需要维护有序数据且保证元素唯一性的场景中表现卓越。通过红黑树实现的高效操作使其成为处理排序、去重、快速查找等需求的理想选择。掌握set的特性和正确使用方式,将显著提升C++编程能力。

九、参考资料

  •  《C++ Primer(第 5 版)》这本书是 C++ 领域的经典之作,对 C++ 的基础语法和高级特性都有深入讲解。
  • 《Effective C++(第 3 版)》书中包含了很多 C++ 编程的实用建议和最佳实践。
  • 《C++ Templates: The Complete Guide(第 2 版)》该书聚焦于 C++ 模板编程,而using声明在模板编程中有着重要应用,如定义模板类型别名等。
  • C++ 官方标准文档:C++ 标准文档是最权威的参考资料,可以查阅最新的 C++ 标准(如 C++11、C++14、C++17、C++20 等)文档。例如,ISO/IEC 14882:2020 是 C++20 标准的文档,可从相关渠道获取其详细内容。
  • :这是一个非常全面的 C++ 在线参考网站,提供了详细的 C++ 语言和标准库文档。
  • :该网站提供了系统的 C++ 教程,配有丰富的示例代码和清晰的解释,适合初学者学习和理解相关知识。
  • 《Effective STL》Scott Meyers

  • 开源项目STL源码分析


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

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

相关文章

[漏洞篇]SSRF漏洞详解

[漏洞篇]SSRF漏洞详解 免责声明&#xff1a; 本文主要讲解漏洞原理&#xff0c;以及防御手段&#xff0c;旨在帮助大家更好的了解漏洞危害&#xff0c;以及开发中所需要的点&#xff0c;切勿拿来做违法事情&#xff0c;否则后果自负。 一、介绍 概念 SSRF&#xff1a;服务端请…

nuscenes数据集分析

nuscenes数据集分析 标注与总体介绍 nuscenes包含有相机、激光雷达、毫米波雷达、IMU与GPS等设备提供的数据。它的数据采集了1000个场景&#xff0c;每个场景大约有20s&#xff0c;针对目标检测任务&#xff0c;对23类物体进行标注&#xff0c;且以2Hz的频率提供精确的三维目标…

JavaScript学习教程,从入门到精通,JavaScript 运算符及语法知识点详解(8)

JavaScript 运算符及语法知识点详解 一、JavaScript 运算符 1. 算术运算符 用于执行数学运算&#xff1a; 加法- 减法* 乘法/ 除法% 取模&#xff08;余数&#xff09; 递增-- 递减** 幂运算&#xff08;ES6&#xff09; let a 10, b 3; console.log(a b); // 13 conso…

Shell脚本的学习

编写脚本文件 定义以开头&#xff1a;#!/bin/bash #!用来声明脚本由什么shell解释&#xff0c;否则使用默认shel 第一步&#xff1a;编写脚本文件 #!/bin/bash #注释 echo "这是输出" 第二步&#xff1a;加上执行权限&#xff1a;chmod x 脚本文件名.sh 第三步&…

在线PDF文件拆分工具,小白工具功能实用操作简单,无需安装的文档处理工具

小白工具中的在线 PDF 文件拆分工具是一款功能实用、操作便捷的文档处理工具&#xff0c;以下是其具体介绍&#xff1a; 操作流程 上传 PDF 文档&#xff1a;打开小白工具在线PDF文件拆分工具 - 快速、免费拆分PDF文档 - 小白工具的在线 PDF 文件拆分页面&#xff0c;通过点击 …

数字的乘阶运算

求数字的乘阶&#xff1a; 例如&#xff1a;6的乘阶运算&#xff1a;6*5*4*3*2*1 例如&#xff1a;3的乘阶运算&#xff1a;3*2*1 class Program{static void Main(string[] args){Console.WriteLine("请输入数字&#xff1a;");int num_01 Convert.ToInt32 (Con…

tcp/ip攻击及防范

作为高防工程师&#xff0c;我每天拦截数以万计的恶意流量&#xff0c;其中TCP/IP协议层攻击是最隐蔽、最具破坏性的威胁之一。常见的攻击手法包括&#xff1a; 1. SYN Flood攻击&#xff1a;攻击者发送大量伪造的SYN包&#xff0c;耗尽服务器连接资源&#xff0c;导致正常用…

C++类成员内存分布详解

本文将探讨C类中成员变量的内存分布情况&#xff0c;包括普通成员、静态成员、虚函数等不同情况下的内存布局。 一、基本成员内存布局 1. 普通成员变量 普通成员变量按照声明顺序在内存中连续排列&#xff08;受访问修饰符和内存对齐影响&#xff09;&#xff1a; class Nor…

计算机视觉——为什么 mAP 是目标检测的黄金标准

概述 在目标检测领域&#xff0c;有一个指标被广泛认为是衡量模型性能的“黄金标准”&#xff0c;它就是 mAP&#xff08;Mean Average Precision&#xff0c;平均精确率均值&#xff09;。如果你曾经接触过目标检测模型&#xff08;如 YOLO、Faster R-CNN 或 SSD&#xff09;…

C语言单链表的增删改补

目录 &#xff08;一&#xff09;单链表的结构定义及初始化 (二)单链表的尾插&#xff0c;头插 (三)单链表的尾删&#xff0c;头删 (四)单链表的查找&#xff0c;删除&#xff0c;销毁 单链表是数据结构课程里的第二个数据结构。单链表在逻辑结构是连续的&#xff0c;在物理…

Android10.0 framework第三方无源码APP读写断电后数据丢失问题解决

1.前言 在10.0中rom定制化开发中,在某些产品开发中,在某些情况下在App用FileOutputStream读写完毕后,突然断电 会出现写完的数据丢失的问题,接下来就需要分析下关于使用FileOutputStream读写数据的相关流程,来实现相关 功能 2.framework第三方无源码APP读写断电后数据丢…

杀戮尖塔(Slay The Spire) 的全新角色模组 - 女巫

女巫&#xff08;The Witch&#xff09; 杀戮尖塔&#xff08;Slay The Spire&#xff09; 的全新角色模组 女巫模组为游戏增添了超过 75 张新卡牌和 4 个全新遗物&#xff0c;围绕 诅咒&#xff08;Curses&#xff09; 展开独特的玩法体验。她的起始遗物 黑猫&#xff08;Bl…

AI开发学习路线(闯关升级版)

以下是一份轻松版AI开发学习路线&#xff0c;用「闯关升级」的方式帮你从零开始变身AI开发者&#xff0c;每个阶段都配有有趣的任务和实用资源&#xff0c;保证不枯燥、可落地&#xff01;&#x1f447; 目录 &#x1f530; 新手村&#xff1a;打基础&#xff08;1-2个月&…

迭代器模式深度解析与实战案例

一、模式定义 迭代器模式&#xff08;Iterator Pattern&#xff09; 是一种行为设计模式&#xff0c;提供一种方法顺序访问聚合对象的元素&#xff0c;无需暴露其底层表示。核心思想是将遍历逻辑从聚合对象中分离&#xff0c;实现 遍历与存储的解耦。 二、核心组件 组件作用…

SSH远程工具

一、常见SSH远程工具 工具开源跨平台多标签文件传输高级功能价格Xshell❌Win✔️✔️脚本、会话管理免费/商业版Tabby✔️全平台✔️✔️插件扩展免费MobaXterm❌Win✔️✔️集成工具集免费/付费SecureCRT❌Win/macOS/Linux✔️✔️企业级加密$129+PuTTY✔️全平台❌❌基础连接…

VUE中的路由处理

1.引入,预处理main.ts import {} from vue-router import { createRouter, createWebHistory } from vue-router import HomePages from @/pages/HomePages.vue import AboutPage from @/pages/AboutPage.vue import NewsPage from @/pages/NewsPage.vue //1. 配置路由规…

编程助手fitten code使用说明(超详细)(vscode)

这两年 AI 发展迅猛&#xff0c;作为开发人员&#xff0c;我们总是追求更快、更高效的工作方式&#xff0c;AI 的出现可以说改变了很多人的编程方式。 AI 对我们来说就是一个可靠的编程助手&#xff0c;给我们提供了实时的建议和解决方&#xff0c;无论是快速修复错误、提升代…

Opencv计算机视觉编程攻略-第九节 描述和匹配兴趣点

一般而言&#xff0c;如果一个物体在一幅图像中被检测到关键点&#xff0c;那么同一个物体在其他图像中也会检测到同一个关键点。图像匹配是关键点的常用功能之一&#xff0c;它的作用包括关联同一场景的两幅图像、检测图像中事物的发生地点等等。 1.局部模板匹配 凭单个像素就…

C++内存管理优化实战:提升应用性能与效率

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家、CSDN平台优质创作者&#xff0c;高级开发工程师&#xff0c;数学专业&#xff0c;拥有高级工程师证书&#xff1b;擅长C/C、C#等开发语言&#xff0c;熟悉Java常用开发技术&#xff0c;能熟练应用常用数据库SQL server,Oracle…

17-产品经理-创建发布

点击“发布”-“创建发布”。 填写发布名称&#xff0c;选择测试的版本。还可以设置此次发布是否为“里程碑”。 点击“保存”后&#xff0c;进入该发布详情页面。需要为此次发布关联需求、已解决BUG、以及遗留BUG。可以通过设置条件&#xff0c;进行“搜索”&#xff0c;然后批…