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

C++ set 容器:有序唯一元素集合的深度解析与实战

在 C++ 标准库中,std::set 是一种基于红黑树实现的有序容器,它专门用于存储唯一且已排序的元素。与序列式容器(如 vectorlist)不同,set 不允许元素重复,且会自动按特定规则排序,非常适合需要快速查找、去重和有序遍历的场景。本文将从底层实现、核心操作到高级应用,全面解析 std::set 的特性与使用技巧。

一、set 的本质与核心特性

1.1 底层实现与核心特征

std::set 属于关联容器(Associative Containers),其核心特性如下:

  • 存储单一类型元素(而非键值对),元素本身即作为排序的依据
  • 元素唯一(不允许重复,若需重复元素可使用 std::multiset
  • 元素按比较规则自动排序(默认按 std::less<T> 升序)
  • 底层基于红黑树(自平衡二叉搜索树)实现,保证插入、删除和查找的平均时间复杂度为 O (log n)

cpp

运行

#include <iostream>
#include <set>
using namespace std;int main() {// 定义一个存储 int 类型的 setset<int> s;// 插入元素(自动去重并排序)s.insert(3);s.insert(1);s.insert(2);s.insert(2);  // 重复元素,插入失败// 遍历输出(自动按升序排列)for (int num : s) {cout << num << " ";}// 输出:1 2 3return 0;
}

1.2 与其他容器的区别

容器类型底层结构元素有序性元素唯一性查找复杂度适用场景
std::set红黑树有序唯一O(log n)有序去重、快速查找
std::multiset红黑树有序可重复O(log n)有序允许重复、分组统计
std::unordered_set哈希表无序唯一平均 O (1)无序去重、追求极致查找效率
std::vector动态数组无序(需手动排序)可重复O(n)随机访问、频繁修改

二、set 的基本操作

2.1 初始化与构造

std::set 支持多种初始化方式,满足不同场景需求:

cpp

运行

#include <set>
#include <vector>
using namespace std;int main() {// 1. 默认构造set<int> s1;// 2. 列表初始化(C++11)set<int> s2 = {3, 1, 4, 1, 5};  // 自动去重排序为 {1,3,4,5}// 3. 范围构造(从其他容器复制元素)vector<int> vec = {2, 7, 1, 8};set<int> s3(vec.begin(), vec.end());  // {1,2,7,8}// 4. 复制构造set<int> s4(s2);  // 复制 s2 的元素// 5. 移动构造(C++11)set<int> s5(std::move(s4));  // s4 变为空// 6. 自定义比较器构造(按降序排列)set<int, greater<int>> s6 = {3, 1, 4};  // {4,3,1}return 0;
}

2.2 插入元素

std::set 提供 insert() 方法插入元素,插入重复元素时会自动忽略:

cpp

运行

#include <set>
#include <iostream>
using namespace std;int main() {set<int> s;// 方式1:插入单个元素s.insert(1);s.insert(2);// 方式2:插入多个元素(C++11 初始化列表)s.insert({3, 4, 5});// 方式3:插入范围int arr[] = {6, 7};s.insert(arr, arr + 2);// 检查插入结果(返回 pair<迭代器, bool>,bool 表示是否插入成功)auto res = s.insert(2);  // 插入重复元素if (res.second) {cout << "插入成功,元素为:" << *res.first << endl;} else {cout << "插入失败(元素已存在),已有元素为:" << *res.first << endl;}// 输出:插入失败(元素已存在),已有元素为:2return 0;
}

注意insert() 的返回值是 pair<iterator, bool>,其中 first 是指向元素的迭代器(无论是否插入成功),second 表示插入是否成功。

2.3 查找与访问元素

std::set 不支持通过下标访问(无 operator[]),需通过迭代器或查找方法访问元素:

cpp

运行

#include <set>
#include <iostream>
using namespace std;int main() {set<int> s = {1, 2, 3, 4, 5};// 方式1:使用 find() 查找(返回迭代器)auto it = s.find(3);if (it != s.end()) {cout << "找到元素:" << *it << endl;  // 找到元素:3} else {cout << "未找到元素" << endl;}// 方式2:检查元素是否存在(C++20 引入 contains())if (s.contains(4)) {  // 等价于 s.find(4) != s.end()cout << "元素 4 存在" << endl;}// 方式3:查找第一个 >= 目标值的元素(lower_bound)auto lower = s.lower_bound(3);cout << "第一个 >= 3 的元素:" << *lower << endl;  // 3// 方式4:查找第一个 > 目标值的元素(upper_bound)auto upper = s.upper_bound(3);cout << "第一个 > 3 的元素:" << *upper << endl;  // 4return 0;
}

2.4 删除元素

std::set 支持通过值、迭代器或范围删除元素:

cpp

运行

#include <set>
#include <iostream>
using namespace std;int main() {set<int> s = {1, 2, 3, 4, 5};// 方式1:通过值删除(返回删除的元素数量,0 或 1)size_t count = s.erase(3);cout << "删除了 " << count << " 个元素" << endl;  // 1// 方式2:通过迭代器删除auto it = s.find(4);if (it != s.end()) {s.erase(it);  // 删除元素 4}// 方式3:删除范围 [first, last)auto start = s.find(1);auto end = s.find(5);s.erase(start, end);  // 删除元素 1、2(不包含 end 指向的 5)// 方式4:清空所有元素s.clear();cout << "清空后元素数量:" << s.size() << endl;  // 0return 0;
}

注意erase(iterator) 会返回下一个有效迭代器(C++11 起),可安全用于循环删除。

2.5 遍历元素

std::set 是有序容器,遍历操作可充分利用其排序特性:

cpp

运行

#include <set>
#include <iostream>
using namespace std;int main() {set<int> s = {3, 1, 4, 1, 5, 9};  // 去重排序后为 {1,3,4,5,9}// 方式1:范围 for 循环(C++11)cout << "正向遍历:";for (int num : s) {cout << num << " ";}// 输出:正向遍历:1 3 4 5 9 // 方式2:迭代器遍历cout << "\n迭代器遍历:";for (set<int>::iterator it = s.begin(); it != s.end(); ++it) {cout << *it << " ";}// 输出:迭代器遍历:1 3 4 5 9 // 方式3:反向迭代器(从大到小)cout << "\n反向遍历:";for (auto it = s.rbegin(); it != s.rend(); ++it) {cout << *it << " ";}// 输出:反向遍历:9 5 4 3 1 return 0;
}

三、set 的比较器与自定义元素类型

3.1 自定义比较器

std::set 默认使用 std::less<T> 按升序排序,可通过自定义比较器改变排序规则:

cpp

运行

#include <set>
#include <iostream>
#include <string>
using namespace std;// 自定义比较器:按字符串长度降序排列
struct StrLenCompare {bool operator()(const string& a, const string& b) const {// 长度不同时,长的在前if (a.size() != b.size()) {return a.size() > b.size();}// 长度相同时,按字典序升序return a < b;}
};int main() {// 使用自定义比较器的 setset<string, StrLenCompare> s;s.insert("apple");    // 5 个字符s.insert("banana");   // 6 个字符s.insert("pear");     // 4 个字符s.insert("grape");    // 5 个字符(与 apple 长度相同)// 遍历输出(按长度降序,长度相同则字典序升序)for (const string& str : s) {cout << str << "(长度:" << str.size() << ")" << endl;}// 输出:// banana(长度:6)// apple(长度:5)// grape(长度:5)// pear(长度:4)return 0;
}

常用比较器

  • std::less<T>:默认,升序
  • std::greater<T>:降序(需包含 <functional>
  • 自定义结构体:支持复杂排序逻辑(如多字段排序)

3.2 存储自定义类型元素

std::set 可存储自定义类型元素,但需定义比较规则(通过比较器或重载 < 运算符):

cpp

运行

#include <set>
#include <iostream>
#include <string>
using namespace std;// 自定义类型:学生
struct Student {int id;string name;int score;// 重载 < 运算符(用于默认排序)bool operator<(const Student& other) const {// 先按分数降序,分数相同按 id 升序if (score != other.score) {return score > other.score;  // 分数高的在前}return id < other.id;  // 分数相同则 id 小的在前}
};int main() {set<Student> students;students.insert({1001, "Alice", 90});students.insert({1002, "Bob", 85});students.insert({1003, "Charlie", 90});  // 与 Alice 分数相同// 遍历输出(按分数降序,同分数按 id 升序)for (const auto& stu : students) {cout << "ID: " << stu.id << ", 姓名: " << stu.name << ", 分数: " << stu.score << endl;}// 输出:// ID: 1001, 姓名: Alice, 分数: 90// ID: 1003, 姓名: Charlie, 分数: 90// ID: 1002, 姓名: Bob, 分数: 85return 0;
}

关键要求:比较规则必须满足严格弱序(Strict Weak Ordering),即:

  • 非自反性:a < a 为 false
  • 传递性:若 a < b 且 b < c,则 a < c
  • 对称性:若 !(a < b) 且 !(b < a),则 a 与 b 视为等价(在 set 中会被视为重复元素)

四、set 的高级操作与应用场景

4.1 范围查询

利用 set 的有序性,可高效查询特定范围内的元素:

cpp

运行

#include <set>
#include <iostream>
using namespace std;int main() {set<int> s = {1, 2, 3, 4, 5, 6, 7, 8, 9};// 查询 [3, 7) 范围内的元素(即 3,4,5,6)auto lower = s.lower_bound(3);  // 第一个 >= 3 的元素auto upper = s.upper_bound(6);  // 第一个 > 6 的元素cout << "范围 [3,7) 内的元素:";for (auto it = lower; it != upper; ++it) {cout << *it << " ";}// 输出:3 4 5 6 return 0;
}

范围查询的时间复杂度:O (log n + k),其中 n 是容器大小,k 是范围内的元素数量。

4.2 与其他容器的转换

set 可与序列式容器(如 vector)相互转换,实现去重和排序:

cpp

运行

#include <set>
#include <vector>
#include <iostream>
using namespace std;int main() {// 1. vector → set(去重排序)vector<int> vec = {3, 1, 4, 1, 5, 9, 2, 6};set<int> s(vec.begin(), vec.end());  // 去重排序为 {1,2,3,4,5,6,9}// 2. set → vector(获取有序无重复的序列)vector<int> sorted_vec(s.begin(), s.end());for (int num : sorted_vec) {cout << num << " ";}// 输出:1 2 3 4 5 6 9 return 0;
}

4.3 常见应用场景

  1. 去重与排序快速对序列去重并按特定规则排序,替代 vector + sort + unique 的组合:

    cpp

    运行

    // 传统方式:vector 去重排序
    vector<int> vec = {3,1,4,1,5};
    sort(vec.begin(), vec.end());
    auto last = unique(vec.begin(), vec.end());
    vec.erase(last, vec.end());// 更简洁的方式:使用 set
    set<int> s(vec.begin(), vec.end());  // 一步完成去重排序
    
  2. 集合运算利用 set 的有序性实现交集、并集、差集等集合操作:

    cpp

    运行

    // 求两个 set 的交集
    set<int> s1 = {1,2,3,4}, s2 = {3,4,5,6};
    set<int> intersection;
    set_intersection(s1.begin(), s1.end(), s2.begin(), s2.end(),inserter(intersection, intersection.begin()));
    // intersection: {3,4}
    
  3. 有序数据的动态维护适用于需要频繁插入、删除并保持有序的场景(如排行榜、日程安排):

    cpp

    运行

    // 维护一个动态有序的分数列表
    set<int, greater<int>> scores;  // 按分数降序
    scores.insert(90);
    scores.insert(85);
    scores.insert(95);
    // 前三名是 95,90,85(直接通过迭代器访问)
    

五、性能分析与最佳实践

5.1 时间复杂度

std::set 核心操作的时间复杂度(n 为元素数量):

  • 插入(insert):O(log n)
  • 查找(findcontains):O(log n)
  • 删除(erase):O(log n)
  • 范围查询(lower_bound 到 upper_bound):O(log n + k)
  • 遍历(begin() 到 end()):O(n)

5.2 空间复杂度

std::set 的空间复杂度为 O (n),每个元素需额外存储红黑树节点的指针(父节点、左右子节点)和颜色标记,内存开销略高于 vector,但低于 unordered_set(无哈希表开销)。

5.3 最佳实践

  1. 优先使用 contains() 检查元素是否存在(C++20)比 find() != end() 更简洁直观:

    cpp

    运行

    if (s.contains(5)) {  // 等价于 s.find(5) != s.end()// 元素存在
    }
    
  2. 批量插入时使用范围插入范围插入(insert(first, last))比逐个插入更高效,红黑树可一次性完成平衡调整:

    cpp

    运行

    vector<int> data = {1,2,3,4,5};
    s.insert(data.begin(), data.end());  // 高效批量插入
    
  3. 避免修改元素的值set 中的元素是 const 类型(修改会破坏排序),若需修改,需先删除旧元素再插入新元素:

    cpp

    运行

    // 错误:元素是 const,无法直接修改
    *s.find(2) = 3;  // 编译错误// 正确:删除旧元素,插入新元素
    auto it = s.find(2);
    if (it != s.end()) {s.erase(it);s.insert(3);
    }
    
  4. 根据场景选择 set 与 unordered_set

    • 需要有序性或范围查询 → 选 set
    • 仅需去重且追求极致查找效率 → 选 unordered_set

六、multiset:允许重复元素的有序集合

std::multiset 是 set 的变体,允许元素重复,其他特性与 set 一致:

cpp

运行

#include <set>
#include <iostream>
using namespace std;int main() {multiset<int> ms = {3, 1, 2, 2, 3, 3};  // 允许重复,自动排序// 遍历输出cout << "所有元素:";for (int num : ms) {cout << num << " ";}// 输出:1 2 2 3 3 3 // 统计元素出现次数int target = 3;auto range = ms.equal_range(target);  // 返回 [lower, upper) 迭代器对int count = distance(range.first, range.second);  // 计算范围内元素数cout << "\n元素 " << target << " 出现了 " << count << " 次" << endl;  // 3 次// 删除所有值为 2 的元素ms.erase(2);  //  erase(value) 删除所有等于 value 的元素cout << "删除 2 后:";for (int num : ms) {cout << num << " ";}// 输出:1 3 3 3 return 0;
}

注意multiset::erase(value) 会删除所有等于 value 的元素,若仅需删除一个,需先通过迭代器定位。

七、总结

std::set 是一种基于红黑树的有序容器,核心特性是元素唯一且自动排序,提供 O (log n) 时间复杂度的插入、查找和删除操作。其优势在于:

  • 无需手动维护排序状态,插入时自动保持有序
  • 高效的范围查询能力,适合需要区间操作的场景
  • 天然支持去重,简化数据处理逻辑

在使用 set 时,需注意元素的 const 特性(不可直接修改)和比较器的严格弱序要求。对于允许重复元素的场景,可使用 multiset;对于无需有序性的场景,unordered_set 通常提供更高的查找效率。

掌握 std::set 的使用,能帮助开发者更优雅地处理有序去重、范围查询等问题,提升代码的可读性和效率。

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

相关文章:

  • 前端的dist包放到后端springboot项目下一起打包
  • Swift 6.2 列传(第六篇):内存安全的 “峨眉戒令”
  • 毕设用别人网站做原型企业英语培训哪里好
  • 网站排名优化系统百度竞价什么意思
  • 网站群项目建设实施进度计划衡水网站建设电话
  • 【自然语言处理】基于混合基的句子边界检测算法
  • 快快测(KKCE)TCping 检测全面升级:IPv6 深度覆盖 + 多维度可视化,重构网络性能监测新体验
  • 句容网站移动互联网软件开发
  • vs编译c语言 | 详细解析如何配置与调试Visual Studio环境
  • 浙江火电建设有限公司网站营销策划公司名字简单大气
  • 自动驾驶与联网车辆网络安全:系统级威胁分析与韧性框架
  • 野火fpga笔记
  • 在 Ubuntu 上安装 MySQL 的详细指南
  • 智慧医疗:FHIR R5、联邦学习与MLOps三位一体的AI产品化实战指南(上)
  • Unity Shader Graph 3D 实例 - 基础的模型颜色渲染
  • 做二手货的网站咋建网站
  • 专业苏州房产网站建设网站定制与模板开发
  • 黄牛群算法详细原理,黄牛群算法公式,黄牛群算法应用
  • html语法
  • 移动终端安全:实验4-中间人攻击
  • 【前端面试】JS篇
  • 网站模板怎么用法企业做pc网站需要什么资料
  • 简单医院网站wordpress xiu 5.5
  • APP上架应用市场全解析:安卓商店与苹果App Store流程及资质要求
  • ECS 事件监控钉钉通知优化实践
  • 2025年ChatGPT Plus、Pro、Business用户Codex使用限制详解(附Codex额度查询入口)
  • Android垃圾回收算法详解
  • wordpress做管理网站百度网盟有哪些网站
  • 东莞企业网站哪家好平顶山网站建设电话
  • 【开题答辩全过程】以 基于Vue的列车信息查询系统为例,包含答辩的问题和答案