当前位置: 首页 > news >正文

【C++】 set/multiset底层原理与逻辑详解

在这里插入图片描述


【C++】 set/multiset底层原理与逻辑详解

  • 摘要
  • 目录
    • 一、`set`
      • 1. 类模板认识
      • 2. 构造函数认识
      • 3. 迭代器和范围for的使用
      • 4.insert 的使用
      • 5. empty 和 size 的使用
      • 6. erase 的使用
      • 7. swap 的使用
      • 8. clear的使用
      • 10.find 的使用
      • 10. count 的使用
      • 11. lower_bound 和upper_bound 的使用
      • 12. equal_range 的使用
    • 二、`multiset`
      • 1. 类模板和构造函数的认识
      • 2. inser的使用
      • 3. erase的使用
      • 4. find的使用
      • 5. count的使用
      • 6. equal_range的使用
  • 总结


摘要

本文详细介绍了C++ STL中的两种关联式容器:setmultiset。这两种容器都基于平衡二叉搜索树(通常是红黑树)实现,能够自动对元素进行排序。set 要求元素唯一,而 multiset 允许重复元素。文章全面讲解了它们的构造函数、迭代器、插入删除操作、查找功能以及各种实用成员函数的使用方法。

键对值详解请点击<----------


目录

一、set

1. 类模板认识

在这里插入图片描述

这张图定义了 std::set 类模板的模板参数。其中,T 表示集合中存储元素的类型;Compare 决定元素的排序方式,默认是升序;Alloc 表示用于分配内存的分配器,默认是标准分配器 std::allocator<T>set 是一个类模板,用于表示有序且元素唯一的集合。

注意:std::set 是按照一定顺序存储元素的容器,其底层实现为平衡二叉搜索树(通常是红黑树)。在 set 中,元素的值就是键(key),类型都是 T,并且每个 key 的值是唯一且不可修改的。因为修改 key 会破坏二叉搜索树的结构,所以 key 只能删除或插入。插入时,如果 key 已经存在,由于唯一性,默认不会插入新的元素。

mapmultimap 不同,set 中只存储 key(即 value),在底层实际存储的是 <value, value> 的形式,每个数据既是键也是值。由于 key 的唯一性,set 可以用于数据去重。用户也可以传入自定义仿函数,实现自己的比较逻辑以得到预期排序结果。


2. 构造函数认识

在这里插入图片描述

默认构造(空集合构造)

explicit set(const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type());

创建一个空的 set 对象。其中comp:用于指定元素排序规则的比较函数对象,默认使用 key_compare(通常是 less)。alloc:用于内存分配的分配器,默认使用 allocator_type(通常是 std::allocator)。特点:explicit 修饰,防止隐式类型转换

区间构造

template <class InputIterator>
set(InputIterator first, InputIterator last,const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type());

通过给定的 [first, last) 范围内的元素来初始化 set。first, last:输入迭代器(左闭右开),指定要插入的元素范围。comp:元素排序规则。alloc:内存分配器。特点:可以一次性用一段已有数据初始化 set。

拷贝构造

set(const set& x);

作用:通过拷贝另一个 set 对象 x 来创建新的 set。复制所有元素及排序规则。新对象与原对象独立,修改其中一个不会影响另一个。

//测试代码
int main()
{//默认构造,创建一个空的set容器set<int> s;//迭代器区间构造,数组初始化setint arr[7] = { 1,3,1,4,5,2,0 };set<int> s1(arr, arr + sizeof(arr) / sizeof(arr[0]));//拷贝构造,根据已有的set创建新的setset<int> s2(s1);return 0;
}

在这里插入图片描述


3. 迭代器和范围for的使用

在这里插入图片描述

int main()
{int arr[7] = { 1,3,1,4,5,2,0 };set<int> s1(arr, arr + sizeof(arr) / sizeof(arr[0]));//正向迭代set<int>::iterator it = s1.begin();while (it != s1.end()){cout << *it << ' ';++it;}cout << endl;//反向迭代set<int>::reverse_iterator rit = s1.rbegin();while (rit != s1.rend()){cout << *rit << " ";rit++;}cout << endl; //44 33 22 11//范围forfor (auto e : s){cout << e << ' ';}cout << endl;return 0;
}

在这里插入图片描述


4.insert 的使用

在这里插入图片描述

  1. 第一个是向set中插入一个单元素val,iterator指向插入的元素(如果这个元素已经存在就指向存在的元素),bool表示是否插入成功),这样set中就不会插入重复元素,并且返回值就能判断是否插入成功。
  2. 第二个是带位置提示的插入,尝试在position(对插入位置的提示)附近插入元素val,返回指向元素的迭代器(如果提示准确可以提高插入效率,反之set仍会按照内部规则寻找正确的位置去插入)。
  3. 第三个是区间插入,将[first,last)范围内的所有元素插入到set中,它将会自动去重和排序。
int main()
{//插入单个数据set<int> s;s.insert(5);s.insert(2);s.insert(0);s.insert(1);s.insert(3);s.insert(1);s.insert(4);//带提示插入set<int> s1(s);auto position = s1.find(5);//找到5的位置,作为插入提示//s1.find(5) 返回的是 set<int>::iterator//带提示的 insert 需要传入 迭代器,而不是整数。所以使用autos1.insert(position, 6);//尝试在position的附近插入6//区间插入int arr[7] = { 1,3,1,4,5,2,0 };set<int> s2(arr, arr + sizeof(arr) / sizeof(arr[0]));return 0;
}

在这里插入图片描述


5. empty 和 size 的使用

在这里插入图片描述

int main()
{set<int> s;cout << "s.size: " << s.size() << endl;cout << "s.empty: " << s.empty() << endl;int arr[7] = { 1,3,1,4,5,2,0 };set<int> s1(arr, arr + sizeof(arr) / sizeof(arr[0]));cout << "s1.size: " << s1.size() << endl;cout << "s1.empty: " << s1.empty() << endl;return 0;
}

在这里插入图片描述


6. erase 的使用

在这里插入图片描述

  1. 删除由迭代器指向的position的位置,删除后position迭代器失效,其他的迭代器仍然存在。
  2. 按值删除元素,返回删除元素的数量。

2.1 对于set来说,相同的元素不可能存在,一个值的元素只可能存在一个。但是为什么不用bool?

erase(const value_type& val) 之所以返回 size_type 而不是 bool,是为了保持 STL 各个关联式容器接口的一致性。因为在 setmap 中元素是唯一的,删除操作最多删除 1 个元素,而在 multisetmultimap 中同一个键可能对应多个元素,删除时可能会移除多个。因此标准库统一返回删除的元素数量(size_type),既能表示删除是否成功,也能在多重容器中反映删除的个数。在 set 中它实际上等价于布尔判断:返回 1 表示删除成功,返回 0 表示未找到目标元素。

  1. 删除迭代器区间[first,last)区间的所有元素,删除后范围内的迭代器失效,其他范围的迭代器仍然有效。
int main()
{int arr[7] = { 1,3,1,4,5,2,0 };set<int> s(arr, arr + sizeof(arr) / sizeof(arr[0]));//删除迭代器指向的位置s.erase(s.begin());for (auto e : s){cout << e << ' ';}cout << endl;//按值删除元素s.erase(5);for (auto e : s){cout << e << ' ';}cout << endl;//删除迭代器范围内的元素s.erase(s.begin(), s.end());for (auto e : s){cout << e << ' ';}cout << endl;return 0;
}

在这里插入图片描述


7. swap 的使用

在这里插入图片描述

交换两个set对象的数据

int main()
{set<int> s;s.insert(1);s.insert(3);s.insert(6);s.insert(8);int arr[7] = { 1,3,1,4,5,2,0 };set<int> s1(arr, arr + sizeof(arr) / sizeof(arr[0]));s.swap(s1);return 0;
}

在这里插入图片描述


8. clear的使用

在这里插入图片描述

清除set对象的数据

int main()
{set<int> s;s.insert(1);s.insert(3);s.insert(6);s.insert(8);int arr[7] = { 1,3,1,4,5,2,0 };set<int> s1(arr, arr + sizeof(arr) / sizeof(arr[0]));s.swap(s1);s1.clear();return 0;
}

在这里插入图片描述


10.find 的使用

在这里插入图片描述

在set容器中查找值为val的元素,如果找到返回指向该元素的迭代器,如果没有找到返回end()。注意:这里的find和标准库的find并不一样。标准库的find是区间遍历查找没时间复杂度为O(N),这里是在二叉搜索数基础上进行了平衡,效率为O(logN)。

int main()
{int arr[7] = { 9,5,34,245,56,88,99 };set<int> s(arr, arr + sizeof(arr) / sizeof(arr[0]));//查找88set<int>::iterator it = s.find(88);if (it != s.end()) //如果不是最后一个元素{cout << *it << endl;s.erase(it);//删除它}else{s.insert(999);//如果是最后一个元素就插入999}for (auto e : s){cout << e << ' ';}cout << endl;return 0;
}

在这里插入图片描述


10. count 的使用

在这里插入图片描述

count 函数用于统计指定值在容器中出现的次数,返回类型为 size_t(即 size_type),因为统计结果是一个非负整数,而 size_t 专门用于表示对象大小或数量,能够适配不同平台下的无符号整数范围,避免溢出或负数结果。在 set 中元素唯一,因此返回值只有 01,但仍用 size_t 是为了与其他容器保持统一接口设计。


11. lower_bound 和upper_bound 的使用

在这里插入图片描述

都是用于在有序容器(如 setmap)中查找位置的函数:

  • lower_bound(val):返回指向第一个不小于 val(即 >= val)的元素的迭代器。
  • upper_bound(val):返回指向第一个大于 val(即 > val)的元素的迭代器。

两者的返回值都是 iterator 类型,表示在容器中对应位置的元素;如果没有符合条件的元素(即到达容器末尾),则返回 end()。在 set 中,它们常用于查找区间范围 [lower_bound(val), upper_bound(val))

int main()
{int arr[] = { 13,31,25,2,77,22,99 };set<int> s(arr, arr + sizeof(arr) / sizeof(arr[0]));for (auto e : s)cout << e << ' ';cout << endl;// lower_bound:返回第一个 >= val 的元素位置// 这里没有 0,但比 0 大的第一个元素是 2set<int>::iterator it_low = s.lower_bound(0);cout << "lower_bound(0) 指向的元素是:" << *it_low << endl;// lower_bound(22):找到第一个 >= 22 的元素,即 22 本身set<int>::iterator it1 = s.lower_bound(22);// upper_bound(77):找到第一个 > 77 的元素,即 99set<int>::iterator it2 = s.upper_bound(77);cout << "lower_bound(22) 指向:" << *it1 << endl;cout << "upper_bound(77) 指向:" << *it2 << endl;// erase(it1, it2):删除 [it1, it2) 区间的所有元素// 由于区间是前闭后开,所以删除的范围是 [22, 77]s.erase(it1, it2);// 再次打印删除后的 setcout << "删除 [22,77] 区间后剩余元素:" << endl;for (auto e : s)cout << e << ' ';cout << endl;return 0;
}

在这里插入图片描述


12. equal_range 的使用

在这里插入图片描述

equal_range 的含义是“相等范围”,即用于查找与指定 key 值相等的一段连续区间的起始与结束位置迭代器。在 set 中,虽然每个 key 值都唯一,不存在真正意义上的“相等序列”,但该函数仍然被保留,是为了与允许重复键值的容器(如 multiset)保持接口一致性。

set 中,equal_range 的返回结果实际上等价于:

{ lower_bound(key), upper_bound(key) }

例如:

  1. set 中的元素为 {1, 5, 7},传入 key = 5 时,equal_range(5) 返回 [5, 7),即起始迭代器指向 5,结束迭代器指向 7。若对该区间调用 erase,会删除值为 5 的元素。

  2. 如果 key 不存在,比如传入 key = 3,则会返回 [5, 5):即起始与结束迭代器都指向比 3 大的第一个元素(5 的位置),代表一个空区间。再进一步,如果传入的 key 大于容器中所有元素,则返回 [end(), end()),同样代表不存在的区间

  3. 因为需要同时返回两个迭代器,equal_range 的返回类型被设计为 pair<iterator, iterator>

    • first 存储区间的起始迭代器;
    • second 存储区间的结束迭代器。
int main()
{//初始化int arr[] = { 1, 5, 7 };set<int> s(arr, arr + sizeof(arr) / sizeof(arr[0]));//打印cout << "当前 set 中的元素为:";for (auto e : s)cout << e << ' ';cout << endl;// ---------- 示例1:key 存在的情况 ----------// equal_range(5):查找 key=5 对应的区间范围pair<set<int>::iterator, set<int>::iterator> range1 = s.equal_range(5);cout << "\n查找 key=5 的区间结果:" << endl;cout << "起始迭代器指向:" << *range1.first << endl;   // 输出 5cout << "结束迭代器指向:" << *range1.second << endl;  // 输出 7(>5 的第一个元素)// 使用区间删除 [5,7)s.erase(range1.first, range1.second);cout << "删除 key=5 后的 set 元素:";for (auto e : s)cout << e << ' ';cout << endl;// ---------- 示例2:key 不存在的情况 ----------// 重新插入数据s.insert(5);cout << "\n重新插入 5 后,set 元素:";for (auto e : s)cout << e << ' ';cout << endl;// equal_range(3):key=3 不存在,会返回 [5,5)pair<set<int>::iterator, set<int>::iterator> range2 = s.equal_range(3);cout << "\n查找 key=3 的区间结果:" << endl;cout << "起始迭代器指向:" << *range2.first << endl;   // 输出 5cout << "结束迭代器指向:" << *range2.second << endl;  // 同样输出 5(因为返回 [5,5))// ---------- 示例3:key 比最大元素还大 ----------pair<set<int>::iterator, set<int>::iterator> range3 = s.equal_range(100);cout << "\n查找 key=100 的区间结果:" << endl;if (range3.first == s.end() && range3.second == s.end())cout << "返回 [end(), end()) —— key 不存在且大于所有元素。" << endl;return 0;
}

在这里插入图片描述


二、multiset

注意:关于multiset的讲解我们主要聚焦于与set的差别部分


1. 类模板和构造函数的认识

在这里插入图片描述

std::multisetstd::set都是类模板,他们的基本使用和接口基本相同,但是set中不能存放相同的key值,但是multiset中可以。

在这里插入图片描述

其类模板和构造函数的认识均可参考上述set的类模板和构造函数的认识。


2. inser的使用

与set不同的是可以插入多个相同的值

#include<iostream>
#include<set>
using namespace std;int main()
{multiset<int> ms;ms.insert(2);ms.insert(0);ms.insert(2);ms.insert(5);ms.insert(2);ms.insert(0);ms.insert(2);ms.insert(5);for (auto e : ms){cout << e << ' ';}cout << endl;return 0;
}

在这里插入图片描述


3. erase的使用

与set不同的是,在删除key值的时候,如果key值有相同的将会全部删除。

ms.erase(5);
for (auto e : ms)
{cout << e << ' ';
}
cout << endl;

在这里插入图片描述

4. find的使用

与set不同的是,当有多个相同的key值,我们使用find查找将会返回通过中序遍历中第一个key值的位置迭代器。


5. count的使用

与set不同的是,当有多个key值的时候,count将会返回总个数。

size_t Count = ms.count(5);
cout << Count << endl;

在这里插入图片描述


6. equal_range的使用

equal_range 的含义是“相等范围”,即用于查找与指定 key 值相等的一段连续区间的起始与结束位置迭代器。在 set 中,虽然每个 key 值都唯一,不存在真正意义上的“相等序列”,但该函数仍然被保留,是为了与允许重复键值的容器(如 multiset)保持接口一致性。

	//equal_range 返回的类型是一个 pair,两个成员分别是迭代器类型。//所以这里定义 p 的类型为 pair<multiset<int>::iterator, multiset<int>::iterator>,// 用来接收返回结果。pair<multiset<int>::iterator, multiset<int>::iterator> p = ms.equal_range(2);cout << *(p.first) << endl; //指向第一个2cout << *(p.second) << endl;//指向第一个大于2的元素

在这里插入图片描述


总结

C++ STL中的setmultiset是基于红黑树实现的有序关联容器,它们核心区别在于set存储唯一元素而multiset允许重复。两者都支持自动排序、O(log n)的高效查找、插入和删除,并提供了丰富的操作接口,包括使用迭代器遍历、插入(insert)、按值或位置删除(erase)、以及findcount等查找功能。特别地,lower_boundupper_bound用于进行范围查找,而equal_range能直接获取某个键的完整范围,这在处理multiset中的重复元素时尤为实用。由于其有序和高效的特性,它们非常适合需要快速查找、自动排序或数据去重(特指set)的场景。


✨ 坚持用 清晰易懂的图解 + 代码语言, 让每个知识点都 简单直观
🚀 个人主页 :不呆头 · CSDN
🌱 代码仓库 :不呆头 · Gitee
📌 专栏系列

  • 📖 《C语言》
  • 🧩 《数据结构》
  • 💡 《C++》
  • 🐧 《Linux》

💬 座右铭“不患无位,患所以立。” 在这里插入图片描述

http://www.dtcms.com/a/614240.html

相关文章:

  • 易语言exe反编译器:深度解析与使用指南
  • 19.优先级队列容器priority_queue
  • 做盗版视频网站成本多少钱低调与华丽wordpress下载
  • JAVA EE初阶 6: 网络编程套接字
  • 旅行网站建设方案策划书wordpress一键优化
  • 自己动手写深度学习框架(优化深度学习框架)
  • C语言编译过程五个步骤 | 深入解析编译过程中的关键环节
  • MATLAB基于云-灰关联分析的教学评价研究
  • 网站由谁备案hyip网站开发
  • 太阳能建设网站y2学年做的租房网站
  • 商场BA楼宇自控系统项目案例
  • React Router
  • 自建网站系统兰州最近事件
  • 【计算机算法设计与分析】动态规划与贪心算法教程:从矩阵连乘到资源优化
  • 智能化时代的SEO关键词优化新策略与实践探索
  • 免费外贸建站平台访问网页的流程
  • 宁夏水利厅建设处网站阳信网站建设
  • 传导案例:某3KW 开关电源整改案例分享
  • 针对特定业务场景(如金融交易、日志处理)选择最优的MPSC实现
  • 练习python题目小记(五)
  • 怎么建立网站 个人云速网站建设公司
  • 怎么用自己电脑做网站社区教育网站建设方案
  • 卫星互联网:弥合数字鸿沟的“天基网络“
  • 选择排序的原理及示例
  • 【开题答辩全过程】以 房产网站为例,包含答辩的问题和答案
  • spring Profile
  • 当AI不再等待指令:智能体工作流如何重构商业逻辑
  • 手机网站建设哪儿好wordpress怎么固定导航栏
  • 基于大数据Python豆瓣电影可视化系统 电影数据爬虫 数据清洗+数据可视化 Flask+requests (MySQL+Echarts 源码+文档)✅
  • 自己建设网站怎么挣钱微信小程序推广软件