【C++ STL】set容器的基本使用

set容器

set 是 C++ STL 中实现有序集合的关联式容器 ,底层基于红黑树(平衡二叉搜索树)实现,核心特性是:元素唯一且自动按升序排序,不支持随机访问,插入 / 删除 / 查找操作的时间复杂度均为 O(logn)。 特征:

  • 底层实现:红黑树(一种自平衡的二叉搜索树),保证元素有序且操作高效。
  • 核心特性
    • 元素不可重复 (自动去重),若需允许重复元素,使用 multiset
    • 元素自动升序排序 (默认按 < 比较,也可自定义排序规则);
    • 不支持直接修改元素(需先删除旧值,再插入新值);
    • 支持迭代器遍历,迭代器为双向迭代器(无 []at() 随机访问)。

基本使用

  • 使用 set 必须包含头文件
c++ 复制代码
#include <set> // 核心头文件
  • 初始化
c++ 复制代码
// 1、默认构造函数,空set(默认升序排列)
set<int> s1;

// 2、初始化列表(c++ 11)
set<int> s2 = { 5, 7, 1, 1, 3, 9 };	// 自动去重 + 升序排列
for (set<int>::iterator it = s2.begin(); it != s2.end(); it++)
{
	cout << *it << " ";		// 输出1 3 5 7 9
}
cout << endl;

// 3、范围构造,从其他容器/迭代器范围初始化
set<int> s3(++s2.begin(), --s2.end());
for (set<int>::iterator it = s3.begin(); it != s3.end(); it++)
{
	cout << *it << " ";		// 输出 3 5 7 
}
cout << endl;

// 4、自定义排序规则(降序)
set<int, greater<int>> s4 = { 3, 5, 7, 9 };
for (set<int>::iterator it = s4.begin(); it != s4.end(); it++)
{
	cout << *it << " ";		// 输出 9 7 5 3 
}
cout << endl;

// 5、拷贝构造
set<int> s5(s3);

核心操作API

  • empty()判断 set 是否为空,返回 bool(空为 true)
  • size()返回元素个数(unsigned int 类型)
  • insert(val)插入元素 val,返回 pair<iterator, bool>:bool 表示是否插入成功(重复则失败)

! 说明 pair<iterator, bool> 是 C++ STL 中 set/map 等关联式容器的 insert()/emplace() 函数的返回值类型,本质是一个「成对的数据结构」,用来同时返回两个信息:插入操作的结果(是否成功)+ 插入 / 找到的元素的迭代器。

  • first:存储第一个数据(对应 set 插入时的「迭代器」);
  • second:存储第二个数据(对应 set 插入时的「是否成功」)。
c++ 复制代码
set<int> s1 = { 5, 7, 1, 1, 3, 9 };

// 插入元素
pair<set<int>::iterator, bool> ret1 = s1.insert(10);
pair<set<int>::iterator, bool> ret2 = s1.insert(1);

for (set<int>::iterator it = s1.begin(); it != s1.end(); it++)
{
	cout << *it << " ";
}
cout << endl;

cout << *ret1.first << endl;	// 10
cout << ret1.second << endl;	// 1

cout << *ret2.first << endl;	// 1
cout << ret2.second << endl;	// 0
  • emplace(val)直接构造元素(效率高于 insert),返回值同 insert
  • erase(pos)删除迭代器 pos 指向的元素
  • erase(val)删除值为 val 的元素,返回删除的个数(set 中只能是 0 或 1)
c++ 复制代码
set<int> s1 = { 5, 7, 1, 1, 3, 9 };

int ret = s1.erase(9);
cout << ret << endl;	// 1
  • erase(beg, end)删除 [beg, end) 范围内的元素
  • clear()清空所有元素find(val)查找值为 val 的元素,返回迭代器:找到则指向该元素,否则指向 end()
  • count(val)统计值为 val 的元素个数(set 中只能是 0 或 1)
  • lower_bound(val)返回第一个 ≥ val 的元素的迭代器
  • upper_bound(val)返回第一个 > val 的元素的迭代器
  • swap(s)交换两个同类型 set 的内容

进阶用法

  • 自定义排序规则
C++ 复制代码
// 自定义降序比较器 
struct MyCompare { 
	bool operator()(int a, int b) const 
	{ 
		return a > b; // 降序排序 
	} 
};

int main() 
{ 
	set<int, MyCompare> s = {5,2,8,1}; // 自定义排序 → {8,5,2,1} cout << "降序遍历:"; 
	for (int num : s) 
	{ 
		cout << num << " "; // 输出:8 5 2 1 } 
		return 0; 
	}
  • 存储自定义数据类型(需要重载比较运算符)

! 注意set 存储自定义结构体 / 类,必须重载 < 运算符(或自定义比较器),否则无法排序; 如果需要自定义数据类型按照降序排列,秩序要该return id > other.id; // 按学号降序

c++ 复制代码
#include <iostream>
#include <set>
#include <string>
using namespace std;

// 自定义结构体:学生(按学号升序排序)
struct Student {
    int id;
    string name;
    // 重载 < 运算符,指定排序规则
    bool operator<(const Student& other) const {
        return id < other.id;  // 按学号升序
    }
};

int main() {
    set<Student> s;
    s.insert({ 2, "Lily" });
    s.insert({ 1, "Tom" });
    s.insert({ 3, "Jack" });

    // 遍历:按学号升序输出
    for (const auto& stu : s) {
        cout << "学号:" << stu.id << ",姓名:" << stu.name << endl;
    }
    return 0;
}
  • multiset(允许重复元素) multisetset 用法几乎一致,唯一区别是允许元素重复,核心差异:
  • insert() 始终返回迭代器(无 bool 值);
  • count(val) 可返回大于 1 的数;
  • erase(val) 会删除所有值为 val 的元素。

注意事项

  • 元素不可修改set 的迭代器是 const_iterator(即使写 iterator,也无法修改元素),因为修改元素会破坏红黑树的有序性。若需修改,需先删除旧值,再插入新值;
  • 查找效率find()count() 更高效(find() 找到即返回,count() 需统计个数),判断元素是否存在优先用 find()
  • 迭代器稳定性:插入 / 删除元素时,除被删除元素的迭代器外,其他迭代器仍有效(红黑树特性)。
  • 空 set 的 end ()find() 找不到元素时返回 end(),不可解引用(*s.end() 会崩溃)。

总结语

  • set 是基于红黑树的有序集合,元素唯一且自动升序,操作复杂度 O(logn),无随机访问;
  • 核心操作:insert()/emplace()(插入)、erase()(删除)、find()(查找)、clear()(清空),遍历仅支持双向迭代器;
  • 自定义类型需重载 < 运算符,允许重复元素用 multiset,修改元素需先删后插。
相关推荐
Quz21 小时前
QML 信号与槽:直接绑定与跨文件通信
qt·编程语言·设计
wenb1n3 天前
MySQL诊断系列(3/6):索引分析——5个SQL揪出“僵尸索引”
数据库·人工智能·编程语言
快乐星空Maker4 天前
C++【生存游戏】开发:荒岛往事 第三期
开发语言·c++·游戏·编程语言
小林ixn5 天前
Python基础全梳理:从注释到函数,这些细节你都掌握了吗?
python·编程语言
甜味弥漫6 天前
一文搞懂 TypeScript:为什么 JavaScript 需要类型?从 JS 到 TS 入门笔记
编程语言
饼干哥哥7 天前
10.5% 深层转化、90%+ 新用户,ChatGPT Ads 值得吗?
算法·正则表达式·编程语言
清秋10 天前
全网最全 ECMAScript 攻略( 更新至 ES2026)
前端·javascript·编程语言
Jack201 个月前
HarmonyOS开发中请求拦截器链:日志、认证、重试
编程语言
Rust研习社1 个月前
组合真的优于继承吗?为什么 Rust 和 Go 都拥抱组合舍弃继承?
后端·rust·编程语言
Jack201 个月前
HarmonyOS开发中RESTful API封装:网络层架构设计
编程语言