【C++进阶】万字长文详解STL set:从基本用法到红黑树底层原理

【C++进阶】万字长文详解STL set:从基本用法到红黑树底层原理

摘要/前言:

在C++开发中,当我们面临“去重”和“排序”的双重需求时,std::set 无疑是首选利器。但你是否想过,为什么它的查找效率是 O(logN)?为什么迭代器遍历是有序的?底层红黑树到底是如何维持平衡的?本文将从基础用法出发,深入剖析 set 的底层红黑树机制,并对比 unordered_set,助你在面试和实战中游刃有余。

目录

  1. 什么是 std::set?
  2. set 的全解
  3. 进阶:自定义类型的排序(仿函数)
  4. 核心:底层数据结构——红黑树解析
  5. 性能对比:set vs unordered_set
  6. 面试高频问答

1. 什么是 std::set?

std::set 是 C++ 标准模板库(STL)中的一种关联式容器
它的核心特性可以用三个词概括:

  • 有序:元素默认按照升序排列。
  • 唯一:容器中不允许存在重复元素(Key 就是 Value)。
  • 高效:插入、删除、查找的时间复杂度均为 O(log⁡N)O(\log N)O(logN)。

头文件:

#include<set>

2. set 的全解

2.1 构造与插入

insert 函数的返回值是一个 pair,包含一个迭代器和一个布尔值,用于判断插入是否成功。

#include<iostream>#include<set>usingnamespace std;intmain(){ set<int> s;// 1. 插入元素 s.insert(10); s.insert(20); s.insert(30); s.insert(10);// 重复插入,无效// 2. 检测插入结果 pair<set<int>::iterator,bool> ret = s.insert(20);if(ret.second){ cout <<"插入成功"<< endl;}else{ cout <<"插入失败,元素已存在"<< endl;// 输出这个}return0;}

2.2 查找与统计

千万不要用全局的 std::find 去查找 set,效率会退化为 O(N)O(N)O(N)。一定要用 set 自带的 find 成员函数

// 查找auto it = s.find(20);if(it != s.end()){ cout <<"找到了: "<<*it << endl;}// 统计(对于set,结果只能是0或1)if(s.count(30)){ cout <<"30存在"<< endl;}

2.3 删除

s.erase(20);// 删除指定值的元素// s.erase(it); // 删除迭代器指向的元素

3. 进阶:自定义类型的排序

这是新手最容易卡壳的地方。如果 set 中存放的是结构体或类,必须告诉 set 如何比较大小。

方式一:重载 operator<

structStudent{ string name;int score;// const 修饰非常重要booloperator<(const Student& other)const{// 按分数降序排列returnthis->score > other.score;}};

方式二:仿函数(Functor)

这种方式更灵活,不需要修改结构体源码。

structComp{booloperator()(const Student& a,const Student& b)const{return a.score > b.score;}};// 使用时指定比较器 set<Student, Comp> st;

4. 核心:底层数据结构——红黑树解析

面试官问 set,其实就是在问红黑树(Red-Black Tree)

4.1 为什么是红黑树?

std::set 底层通常实现为红黑树。它是一种自平衡的二叉搜索树(BST)

  • 为什么不用普通二叉搜索树?
    如果插入的数据是有序的(如 1, 2, 3, 4, 5),普通 BST 会退化成链表,查找效率从 O(log⁡N)O(\log N)O(logN) 降为 O(N)O(N)O(N)。
  • 为什么不用 AVL 树?
    AVL 树是“绝对平衡”的,查询效率极高,但为了维持绝对平衡,每次插入删除都需要大量的旋转操作。红黑树是“弱平衡”的,在插入删除的性能和查询性能之间取得了更好的折中。

4.2 红黑树的 5 条规则

红黑树通过以下规则保证最长路径不会超过最短路径的 2 倍:

  1. 每个节点不是红色就是黑色
  2. 根节点必须是黑色
  3. 如果一个节点是红色的,则它的两个子节点必须是黑色(不能有连续的红节点)。
  4. 对于每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点
  5. 每个叶子节点(NIL节点)都是黑色的。

4.3 迭代器稳定性

set 的迭代器本质上是红黑树节点的指针。

  • 重要特性: 除了被删除的那个元素的迭代器外,set 的插入和删除操作不会导致其他迭代器失效。这与 vector 扩容导致所有迭代器失效完全不同。

5. 性能对比:set vs unordered_set

这是面试中的必考题,一张表讲清楚:

特性std::setstd::unordered_set
底层实现红黑树 (RB-Tree)哈希表 (Hash Table)
是否有序有序无序
查找/插入时间O(log⁡N)O(\log N)O(logN)平均 O(1)O(1)O(1),最坏 O(N)O(N)O(N)
内存占用较低(存节点指针)较高(需维护Bucket数组)
适用场景需要数据有序、范围查找纯粹的快速查找、去重

选择建议:

  • 如果你需要遍历输出有序数据,或者进行 lower_bound/upper_bound 范围查找,必选 set
  • 如果你只是为了单纯的快速去重,不在乎顺序,unordered_set

6. 面试高频问答 (Q&A)

Q1: set 中可以通过迭代器修改元素的值吗?

A:不可以。set 中的元素是 const 的。因为修改元素值可能会破坏红黑树的有序性。如果必须修改,正确的做法是:先 erase 旧元素,再 insert 新元素。

Q2: 红黑树查找的时间复杂度是多少?

A:O(log⁡N)O(\log N)O(logN)。因为红黑树的高度近似为 2log⁡N2\log N2logN。

Q3: 为什么 map/set 的插入效率比 vector 高?

A: 虽然 vector 尾插很快,但如果在中间插入,vector 需要移动后续所有数据。而 map/set 只需要修改节点指针和进行少量的旋转变色,不需要内存拷贝。

总结

std::set 是 C++ 程序员必须掌握的容器。理解其背后的红黑树原理,不仅能帮你写出更高效的代码,更是通往高阶开发的敲门砖。

如果你觉得这篇文章对你有帮助,欢迎点赞、收藏、关注!如有疑问,请在评论区留言。

Read more

uv终极技巧:一招精准指定Python版本,告别版本混乱!

还在为不同项目间Python版本冲突而烦恼?掌握uv的版本指定技巧,让每个项目都运行在“量身定制”的解释器环境中! 摘要 本文将深入解析在使用uv进行Python项目管理时,如何在不同场景下精准指定Python版本。从项目初始化、现有项目版本切换到全局版本管理,你将掌握一套完整的Python版本控制方案,彻底解决“我的代码需要Python 3.9,但系统默认是3.11”这类经典问题。 🎯 为什么需要指定Python版本? 在真实开发中,指定Python版本至关重要: * 依赖兼容性:某些包仅支持特定Python版本 * 团队统一:确保所有开发者使用相同版本 * 生产一致性:避免开发与生产环境版本不一致导致的Bug * 多版本测试:验证代码在不同Python版本下的表现 🚀 三大场景实战指南 场景一:创建新项目时指定版本(最常用) 在项目初始化阶段指定Python版本是最佳实践: # 方式1:使用 --python 参数直接指定 uv init --python 3.9# 这将创建一个使用Python 3.9的新项目# 方式2:指定精确版本 uv in

By Ne0inhk

鸿蒙PC开发全面开花!仓颉/Java/Python等多语言适配,解锁全场景开发新体验

鸿蒙PC开发全面开花!仓颉/Java/Python等多语言适配,解锁全场景开发新体验 随着鸿蒙操作系统(HarmonyOS)生态的持续演进,PC端已成为其全场景战略的核心阵地之一。如今,鸿蒙PC已实现对仓颉、rkTS、Java、JavaScript、Python、C/C++等主流编程语言的全面支持,为开发者提供了多元化的技术选型空间。无论是深耕系统底层开发,还是专注应用快速迭代,亦或是探索AI与全场景交互,都能在鸿蒙PC开发生态中找到契合的解决方案。本文将带你走进鸿蒙PC的多语言开发世界,从语言特性解析到适配能力解读,最后邀你一同尝鲜这场全场景开发革命。 一、先搞懂:这些编程语言各有何神通? 在切入鸿蒙PC的支持特性前,我们先快速梳理这六种语言的核心定位与优势。不同语言的设计理念差异,决定了它们在鸿蒙PC生态中的适配场景与价值,也让开发者能根据项目需求精准选型。 1. 仓颉:鸿蒙生态的“原生灵魂” 作为华为自研的现代编程语言,仓颉是鸿蒙生态的核心专属语言,专为全场景智能应用开发而生。它兼具高效编程、安全可靠、轻松并发和卓越性能四大核心特性,支持函数式、命令式、面向对象等多编

By Ne0inhk
Qt for Python:PySide6 入门指南(中篇)

Qt for Python:PySide6 入门指南(中篇)

Qt for Python:PySide6 入门指南(上篇) 本文详细介绍 Qt Widgets 开发。 一、基础示例 import sys from PySide6.QtWidgets import QApplication, QLabel app = QApplication(sys.argv) label = QLabel("Hello World!") label.show() app.exec() 运行效果: 对于使用 PySide6 的 Widget 应用程序,我们必须先从 PySide6.QtWidgets 模块中导入相应的类,导入完成后,需要创建一个 QApplication 实例。 由于 Qt

By Ne0inhk
Python快速入门指南:从零开始掌握Python编程

Python快速入门指南:从零开始掌握Python编程

文章目录 * 前言 * 一、Python环境搭建🥏 * 1.1 安装Python * 1.2 验证安装 * 1.3 选择开发工具 * 二、Python基础语法📖 * 2.1 第一个Python程序 * 2.2 变量与数据类型 * 2.3 基本运算 * 三、Python流程控制🌈 * 3.1 条件语句 * 3.2 循环结构 * 四、Python数据结构🎋 * 4.1 列表(List) * 4.2 字典(Dictionary) * 4.3 元组(Tuple)和集合(Set) * 五、函数与模块✨

By Ne0inhk