目录
-
- [1. 引入:从序列式到关联式容器](#1. 引入:从序列式到关联式容器)
- [2. set 的设计](#2. set 的设计)
- [3. 核心接口](#3. 核心接口)
-
- [3.1 构造函数 (Constructor)](#3.1 构造函数 (Constructor))
- [3.2 迭代器操作 (Iterator)](#3.2 迭代器操作 (Iterator))
- [3.3 容量操作 (Capacity)](#3.3 容量操作 (Capacity))
- [3.4 修改与查询操作 (Modifiers & Operations)](#3.4 修改与查询操作 (Modifiers & Operations))
- [4. 核心操作](#4. 核心操作)
-
- [4.1 插入与遍历(自动去重排序)](#4.1 插入与遍历(自动去重排序))
- [4.2 查找与区间操作](#4.2 查找与区间操作)
- [4.3 multiset:不去重的变体](#4.3 multiset:不去重的变体)
- [5. 深度剖析:底层引擎为什么是红黑树?](#5. 深度剖析:底层引擎为什么是红黑树?)
-
- [5.1 放弃 AVL 树的原因](#5.1 放弃 AVL 树的原因)
- [5.2 红黑树的妥协与优势](#5.2 红黑树的妥协与优势)
- [5.3 迭代器底层的精妙设计](#5.3 迭代器底层的精妙设计)
- [附录:基于红黑树封装自定义 set](#附录:基于红黑树封装自定义 set)
1. 引入:从序列式到关联式容器
在 C++ STL 中,vector、list、deque 等被称为序列式容器 ,其底层是线性的数据结构,存储的是元素本身。而为了解决海量数据下的高效率检索问题,STL 引入了关联式容器。
关联式容器的核心在于存储的是 <key, value> 结构的键值对 。在底层,键值对通过 std::pair 结构体实现,包含代表键值的 key (即 first) 和对应信息的 value (即 second)。
cpp
// STL 中键值对的底层定义
template <class T1, class T2>
struct pair
{
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair(): first(T1()), second(T2())
{}
pair(const T1& a, const T2& b): first(a), second(b)
{}
};
2. set 的设计

根据底层结构的不同,关联式容器分为树型结构(如 set、map)和哈希结构。set 作为树形关联容器的代表,其设计具有以下核心特征:
-
真正的存储结构 :表面上
set只对外暴露value,但其底层实际存放的是由<value, value>构成的键值对。 -
元素天然有序且唯一 :
set内部根据特定的严格弱排序准则(默认按小于升序)对元素进行排序,且每个value必须唯一。 -
元素绝对禁止修改(const) :
set中的元素在容器中总是const的。深度思考:为什么不允许修改? 因为set的底层是二叉搜索树,如果允许修改结点的key,会直接破坏树的有序性与严格弱排序准则,导致整棵树失效。 -
时间复杂度 :依靠平衡树支撑,查找、插入和删除的时间复杂度均严格稳定在 O ( log 2 N ) O(\log_2 N) O(log2N)。
3. 核心接口
3.1 构造函数 (Constructor)
| 函数声明 | 功能介绍 |
|---|---|
set (const Compare& comp = Compare(), const Allocator& = Allocator() ); |
构造空的 set |
set (InputIterator first, InputIterator last, ...); |
用 [first, last) 区间中的元素构造 set |
set (const set<Key,Compare,Allocator>& x); |
set 的拷贝构造 |
3.2 迭代器操作 (Iterator)
| 函数声明 | 功能介绍 |
|---|---|
iterator begin() / iterator end() |
返回正向迭代器(begin 指向首元素,end 指向尾元素下一个位置) |
const_iterator cbegin() / cend() |
返回 const 版本的正向迭代器 |
reverse_iterator rbegin() / rend() |
返回反向迭代器(rbegin 即 end,rend 即 begin) |
const_reverse_iterator crbegin() / crend() |
返回 const 版本的反向迭代器 |
3.3 容量操作 (Capacity)
| 函数声明 | 功能介绍 |
|---|---|
bool empty() const |
检测 set 是否为空,空返回 true,否则返回 false |
size_type size() const |
返回 set 中有效元素的个数 |
3.4 修改与查询操作 (Modifiers & Operations)
| 函数声明 | 功能介绍 |
|---|---|
pair<iterator,bool> insert(const value_type& x) |
在 set 中插入元素 x,返回 <该元素位置, 是否插入成功>(若已存在则返回 false) |
iterator erase (const_iterator position) |
删除 position 位置上的元素 |
size_type erase (const key_type& x) |
删除 set 中值为 x 的元素,返回删除的元素个数 |
iterator erase (const_iterator first, const_iterator last) |
删除 set 中 [first, last) 区间中的元素 |
void swap (set<Key, Allocator Compare,>& st) |
交换两个 set 中的元素 |
void clear () |
将 set 中的元素清空 |
iterator find (const key_type& x) const |
返回 set 中值为 x 的元素的位置(迭代器),找不到则返回 end() |
size_type count (const key_type& x) const |
返回 set 中值为 x 的元素的个数(对于 set 只能是 0 或 1) |
4. 核心操作
4.1 插入与遍历(自动去重排序)
在 set 中插入元素时,无需显式构造键值对,直接传入 value 即可。
cpp
#include <iostream>
#include <set>
using namespace std;
int main()
{
// 去重 + 排序
set<int> s;
s.insert(5);
s.insert(2);
s.insert(7);
s.insert(4);
s.insert(9);
s.insert(9); // 重复插入无效
s.insert(9);
s.insert(1);
auto it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
// 范围 for 遍历
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
return 0;
}
4.2 查找与区间操作
必须认清算法库中的 std::find 与 set::find 的本质区别:
cpp
// 1. 算法库的 find,底层暴力遍历,时间复杂度 O(N)
auto pos1 = find(s.begin(), s.end(), x);
// 2. set 成员函数 find,利用红黑树查找,时间复杂度 O(log_2 N)
auto pos2 = s.find(x);
对于区间操作,set 提供了 lower_bound(返回 ≥ \ge ≥ 目标的迭代器)和 upper_bound(返回 > > > 目标的迭代器),极大地简化了左闭右开 [first, last) 区间的删除操作。
cpp
int main()
{
set<int> myset;
set<int>::iterator itlow, itup;
for (int i = 1; i < 10; i++) myset.insert(i * 10); // 10 20 30 40 50 60 70 80 90
itlow = myset.lower_bound(30); // >= 30
itup = myset.upper_bound(60); // > 60
// 删除 [30, 60]
myset.erase(itlow, itup); // 剩余: 10 20 70 80 90
for (set<int>::iterator it = myset.begin(); it != myset.end(); ++it)
cout << ' ' << *it;
cout << endl;
return 0;
}
4.3 multiset:不去重的变体

cpp
int main()
{
multiset<int> s;
s.insert(1);
s.insert(10);
s.insert(15);
s.insert(14);
s.insert(14);
s.insert(14);
for (auto w : s)
{
cout << w << " ";
}
cout << endl << s.count(14) ;
return 0;
}

如果业务场景仅需排序而不需要去重,可以使用 multiset。其接口与 set 基本一致,底层同样存放 <value, value>,但允许元素重复。此时调用 count(x) 能够返回元素出现的实际次数,而不再局限于 0 或 1。
5. 深度剖析:底层引擎为什么是红黑树?
set 和 map 的底层均为红黑树 (Red-Black Tree),而不是 AVL 树。
5.1 放弃 AVL 树的原因
AVL 树是绝对平衡的二叉搜索树,要求每个结点的左右子树高度差绝对值不超过 1。这保证了极高的查询效率 O ( log 2 N ) O(\log_2 N) O(log2N)。但是,在频繁增删结点的场景下,AVL 树为了维持这种绝对平衡,需要进行大量的旋转操作,甚至在删除时旋转可能持续到根结点,性能开销极大。
5.2 红黑树的妥协与优势
红黑树通过颜色约束牺牲了部分平衡性,来换取更少旋转次数:
-
每个结点不是红色就是黑色。
-
根结点必须是黑色。
-
不能有连在一起的红色结点。
-
每条路径上的黑色结点数目必须相同。
这些性质确保了红黑树的最长路径不会超过最短路径的两倍 ,达成了一种"近似平衡"。它的增删改查时间复杂度依然是 O ( log 2 N ) O(\log_2 N) O(log2N),但由于旋转次数远少于 AVL 树,在实际应用(如 C++ STL、Linux 内核)中具有更高的综合性能。
5.3 迭代器底层的精妙设计
STL 规定 begin() 和 end() 构成前闭后开的区间。在中序遍历红黑树时,begin() 应当是最小结点(最左侧结点),那 end()(最大结点的下一个位置)应该指向哪里?不能简单设为 nullptr,因为还要支持对 end() 迭代器进行 -- 操作找回最后一个元素。
STL 的红黑树实现中,巧妙地增加了一个黑色的头结点 (header):
-
header->_pParent指向红黑树真实的root。 -
header->_pLeft指向树中最小的结点(即begin())。 -
header->_pRight指向树中最大的结点。 -
end()迭代器直接指向这个header结点。
这种设计完美闭环了整棵树的迭代逻辑。
附录:基于红黑树封装自定义 set
为了证明底层结构与表层 API 的关系,我们可以通过复用泛型红黑树(RBTree)来模拟实现一个完整的 set。
cpp
#include <functional> // 为了引入 std::less
namespace bit
{
// 增加 Compare 模板参数,默认使用 less<K>
template<class K, class Compare = std::less<K>>
class set
{
typedef K ValueType;
struct KeyOfValue
{
const K& operator()(const ValueType& key) const // 注意加 const
{
return key;
}
};
// 将 Compare 也传给底层的红黑树
typedef RBTree<K, ValueType, KeyOfValue, Compare> RBTree_t;
public:
// 关键:set 的迭代器统一使用红黑树的 const 迭代器
typedef typename RBTree_t::ConstIterator iterator;
typedef typename RBTree_t::ConstIterator const_iterator;
public:
set() {}
// 提供 const 版本的迭代器接口
iterator begin() const { return _t.Begin(); }
iterator end() const { return _t.End(); }
size_t size() const { return _t.Size(); }
bool empty() const { return _t.Empty(); }
// 注意:底层 Insert 如果返回 pair<RBTree_t::Iterator, bool>
// 这里可能需要做一个隐式或显式的转换,转成 pair<iterator, bool>
pair<iterator, bool> insert(const ValueType& data)
{
return _t.Insert(data);
}
void clear() { _t.Clear(); }
iterator find(const K& key) const // 提供 const 版本的 find
{
return _t.Find(key);
}
private:
RBTree_t _t;
};
}