侯捷 C++ 课程学习笔记:set与multiset深度求索

个人简介
在这里插入图片描述
作者简介:全栈研发,具备端到端系统落地能力,专注大模型的压缩部署、多模态理解与 Agent 架构设计。 热爱“结构”与“秩序”,相信复杂系统背后总有简洁可控的可能。
我叫观熵。不是在控熵,就是在观测熵的流动
个人主页:观熵
个人邮箱:privatexxxx@163.com
座右铭:愿科技之光,不止照亮智能,也照亮人心!

专栏导航

观熵系列专栏导航:
AI前沿探索:从大模型进化、多模态交互、AIGC内容生成,到AI在行业中的落地应用,我们将深入剖析最前沿的AI技术,分享实用的开发经验,并探讨AI未来的发展趋势
AI开源框架实战:面向 AI 工程师的大模型框架实战指南,覆盖训练、推理、部署与评估的全链路最佳实践
计算机视觉:聚焦计算机视觉前沿技术,涵盖图像识别、目标检测、自动驾驶、医疗影像等领域的最新进展和应用案例
国产大模型部署实战:持续更新的国产开源大模型部署实战教程,覆盖从 模型选型 → 环境配置 → 本地推理 → API封装 → 高性能部署 → 多模型管理 的完整全流程
TensorFlow 全栈实战:从建模到部署:覆盖模型构建、训练优化、跨平台部署与工程交付,帮助开发者掌握从原型到上线的完整 AI 开发流程
PyTorch 全栈实战专栏: PyTorch 框架的全栈实战应用,涵盖从模型训练、优化、部署到维护的完整流程
深入理解 TensorRT:深入解析 TensorRT 的核心机制与部署实践,助力构建高性能 AI 推理系统
Megatron-LM 实战笔记:聚焦于 Megatron-LM 框架的实战应用,涵盖从预训练、微调到部署的全流程
AI Agent:系统学习并亲手构建一个完整的 AI Agent 系统,从基础理论、算法实战、框架应用,到私有部署、多端集成
DeepSeek 实战与解析:聚焦 DeepSeek 系列模型原理解析与实战应用,涵盖部署、推理、微调与多场景集成,助你高效上手国产大模型
端侧大模型:聚焦大模型在移动设备上的部署与优化,探索端侧智能的实现路径
行业大模型 · 数据全流程指南:大模型预训练数据的设计、采集、清洗与合规治理,聚焦行业场景,从需求定义到数据闭环,帮助您构建专属的智能数据基座
机器人研发全栈进阶指南:从ROS到AI智能控制:机器人系统架构、感知建图、路径规划、控制系统、AI智能决策、系统集成等核心能力模块
人工智能下的网络安全:通过实战案例和系统化方法,帮助开发者和安全工程师识别风险、构建防御机制,确保 AI 系统的稳定与安全
智能 DevOps 工厂:AI 驱动的持续交付实践:构建以 AI 为核心的智能 DevOps 平台,涵盖从 CI/CD 流水线、AIOps、MLOps 到 DevSecOps 的全流程实践。
C++学习笔记?:聚焦于现代 C++ 编程的核心概念与实践,涵盖 STL 源码剖析、内存管理、模板元编程等关键技术
AI × Quant 系统化落地实战:从数据、策略到实盘,打造全栈智能量化交易系统


侯捷 C++ 课程学习笔记:set与multiset深度求索


一、前言:为何深挖 set/multiset?

在 C++ STL 中,setmultiset 是两种非常重要的关联式容器(associative containers),它们与 map/multimap 一同构成了有序容器家族的核心。而相较于频繁使用的 vectorunordered_mapsetmultiset 显得“安静”得多。

但通过侯捷老师对底层机制的深度剖析,我意识到:

  • set/multiset 蕴含了平衡树的实现精髓;
  • 它们的操作时间复杂度优雅地控制在 O(log n);
  • 它们在需要自动排序、去重、多重数据统计的场景中扮演重要角色。

本文将从使用、实现、源码分析、性能测试、工程建议五个角度,对 setmultiset 进行系统性探索。


二、使用入门:自动排序 + 唯一性控制

1. set 是“自动去重”的有序容器

#include <iostream>
#include <set>

int main() {
    std::set<int> s = {5, 2, 4, 2, 1};

    for (int x : s)
        std::cout << x << " ";
}

输出:

1 2 4 5

特点总结:

  • 内部自动排序(默认按 operator<);
  • 元素唯一(自动去重);
  • 插入、删除、查找都是 O(log n);

2. multiset:允许重复,适合统计场景

std::multiset<int> ms = {5, 2, 2, 4, 5};

for (int x : ms)
    std::cout << x << " ";

输出:

2 2 4 5 5

3. 查找和统计接口

std::set<int> s = {1, 3, 5, 7};

auto it = s.find(5); // 返回指向5的迭代器
bool exists = s.count(6); // 0

对于 multiset:

ms.count(5); // 可返回重复数量

三、底层实现探秘:红黑树的精妙机制

set 的底层 = 红黑树(Red-Black Tree)

侯捷在 STL 源码课程中讲解:所有 set/multiset 均基于红黑树实现,而不是 AVL 树或 Treap,原因如下:

数据结构平衡类型插入/删除旋转次数查找性能实现复杂度
AVL 树强平衡
红黑树弱平衡稍慢

STL 设计更注重综合效率,所以选用红黑树。

红黑树的五大性质(简述)

  1. 每个节点非红即黑;
  2. 根节点为黑色;
  3. 每个红节点的子节点均为黑色(无连续红);
  4. 从任意节点到叶子路径,黑色节点数相同;
  5. 插入和删除通过旋转和染色维护平衡。

四、源码结构解析:set 是如何包裹 RB-tree 的?

侯捷课程中详细解构了 SGI STL 中 set 的实现,源码抽象如下:

template <typename Key, typename Compare = less<Key>, typename Alloc = allocator<Key>>
class set {
public:
    typedef Key value_type;
private:
    rb_tree<value_type, value_type, identity<value_type>, Compare, Alloc> t;
};

关键点:

  • set 是对 rb_tree 的封装;
  • value_typekey_type 相同;
  • 插入/查找都委托给 rb_tree 的对应函数;

插入示意:

std::set<int> s;
s.insert(5);

底层等价于:

t.insert_unique(5); // insert_unique 是红黑树中的“唯一插入”

对于 multiset

t.insert_equal(5); // 允许重复的插入

五、插入机制演绎:红黑树的动态平衡过程

插入节点过程(示意):

1. 插入为红色节点;
2. 若违反红黑树规则(例如连续红),则:
    - 染色;
    - 旋转(左旋/右旋);
3. 最终维持五大性质。

侯捷课程演示的经典左旋:

// 假设插入造成连续红,需要调整
node* rotate_left(node* x) {
    node* y = x->right;
    x->right = y->left;
    y->left = x;
    return y;
}

六、迭代器与遍历特性:有序性 + 稳定性

std::set<int> s = {1, 3, 2, 4};

for (auto it = s.begin(); it != s.end(); ++it)
    std::cout << *it << " ";

输出(总是升序):

1 2 3 4

C++17 后支持结构化绑定遍历 map / set

for (auto&& [x] : s) { std::cout << x; }

七、实测对比:性能如何?

我们测试在插入、查找 10 万个元素场景下的性能:

测试代码片段

#include <set>
#include <unordered_set>
#include <chrono>

void test_set() {
    std::set<int> s;
    for (int i = 0; i < 100000; ++i)
        s.insert(i);

    for (int i = 0; i < 100000; ++i)
        s.find(i);
}

结果示意(单位:ms)

容器插入时间查找时间
set40ms20ms
unordered_set25ms5ms
vector+sort+binary_search15ms(排序)20ms

结论:

  • unordered_set 查找最优,但无序;
  • set 有序,查找次优;
  • vector 需手动维护有序性;

八、set 与 multiset 的应用场景与对比

特性setmultiset
自动排序
元素唯一
元素统计(频率)✅(支持count)
插入相同值行为忽略插入多个
查找/删除单值find/erasefind/erase_all

示例场景:

  • 维护一个自动排序不重复的学生 ID 集合:set
  • 多人评分数据,允许重复:multiset
  • 按优先顺序输出数据:set + 自定义比较器

九、自定义排序规则:灵活多变的比较器

struct Greater {
    bool operator()(int a, int b) const {
        return a > b;
    }
};

std::set<int, Greater> s = {1, 4, 2};

输出为降序:

4 2 1

示例:按字符串长度排序

struct ByLength {
    bool operator()(const std::string& a, const std::string& b) const {
        return a.size() < b.size();
    }
};

std::set<std::string, ByLength> s = {"a", "abc", "ab"};

输出:

a ab abc

十、工程实战建议:何时选用 set/multiset?

场景建议容器
需要自动排序,频繁查找set
要维护多个重复值,统计频率multiset
要自定义排序逻辑(如按时间/长度等)set<..., Comp>
高并发读写(线程安全)不推荐 STL 容器,可考虑 concurrent map
无需排序,追求查找极致性能unordered_set

十一、源码模拟:手写简易 set

template <typename T>
class MySet {
private:
    std::set<T> tree; // 实际封装红黑树或 map
public:
    bool insert(const T& val) {
        return tree.insert(val).second;
    }

    bool exists(const T& val) {
        return tree.find(val) != tree.end();
    }

    void erase(const T& val) {
        tree.erase(val);
    }

    void print() {
        for (auto& x : tree) std::cout << x << " ";
    }
};

十二、侯捷课程中的设计哲学总结

侯捷老师强调:

“STL 是对数据结构的最合理抽象;set 是集合论与数据结构融合后的优雅表达。”

我们从中能学到:

  • STL 的容器并不只是提供“功能”,它体现“行为约束”
  • 底层红黑树的存在,让 set 的操作始终保持平衡与稳定性
  • 泛型编程、模板参数、比较器策略,才是 C++ 高级编程的灵魂所在

十三、结语:set 和 multiset,不止是容器,更是策略

在泛型算法、排序查找、统计管理、工程项目中,setmultiset 提供了极大的便利。而它们的强大之处来自:

  • 红黑树的平衡性保障
  • 自动排序 + 元素管理的强封装
  • 迭代器稳定性与泛型接口


如果你觉得这篇博客有帮助,欢迎点赞、评论、收藏,或在下方留言你对 set / multiset 的使用经验!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

观熵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值