文章目录
- [1. 序列式容器和关联式容器(了解)](#1. 序列式容器和关联式容器(了解))
- [2. set系列的使用](#2. set系列的使用)
-
- [2.1 set类的介绍](#2.1 set类的介绍)
- [2.2 set的构造和迭代器](#2.2 set的构造和迭代器)
-
- 0.构造:
- [1. 空构造(empty (1))](#1. 空构造(empty (1)))
- [2. 范围构造(range (2))](#2. 范围构造(range (2)))
- [3. 拷贝构造(copy (3))](#3. 拷贝构造(copy (3)))
- [4. 初始化列表(C++11)](#4. 初始化列表(C++11))
- [2.3 修改器(Modifiers)的成员函数](#2.3 修改器(Modifiers)的成员函数)
-
- [0. 迭代器](#0. 迭代器)
- [1. insert:插入元素](#1. insert:插入元素)
- [2. erase:删除元素](#2. erase:删除元素)
- [3. swap:交换两个set的内容(与算法库swap对比)](#3. swap:交换两个set的内容(与算法库swap对比))
- [4. clear:清空所有元素](#4. clear:清空所有元素)
- [5. emplace:构造并插入元素(C++11+)](#5. emplace:构造并插入元素(C++11+))
- [6. emplace_hint:带位置提示的构造插入(C++11+)](#6. emplace_hint:带位置提示的构造插入(C++11+))
- [2.4 find(与算法库find的对比)](#2.4 find(与算法库find的对比))
- [2.5 key_comp && value_comp](#2.5 key_comp && value_comp)
- [2.6 count(与find比较)](#2.6 count(与find比较))
- [2.7 lower_bound && upper_bound](#2.7 lower_bound && upper_bound)
- [3. multiset](#3. multiset)
-
- [3.1 核心特性差异](#3.1 核心特性差异)
- [3.2 接口行为差异](#3.2 接口行为差异)
- [3.3 使用场景差异](#3.3 使用场景差异)
- [4. 例题部分](#4. 例题部分)
-
- [4.1 环形链表 II](#4.1 环形链表 II)
- [4.2 两个数组的交集](#4.2 两个数组的交集)
1. 序列式容器和关联式容器(了解)
前面我们已经接触过STL中的部分容器如:string、vector、list、deque、array、forward_list等,这些容器统称为序列式容器,因为逻辑结构为线性序列的数据结构,两个位置存储的值之间一般没有紧密的关联关系,比如交换一下,他依旧是序列式容器。顺序容器中的元素是按他们在容器中的存储位置来顺序保存和访问的。
关联式容器也是用来存储数据的,与序列式容器不同的是,关联式容器逻辑结构通常是非线性结构,两个位置有紧密的关联关系,交换一下,他的存储结构就被破坏了。顺序容器中的元素是按关键字来保存和访问的。关联式容器有map/set系列和unordered_map/unordered_set系列。
map和set底层是红黑树,红黑树是一颗平衡二叉搜索树。set是key搜索场景的结构,map是key/value搜索场景的结构。
说人话 就是map set的值不能改 改了结构会被破坏。
2. set系列的使用
2.1 set类的介绍
set的声明如下,T就是set底层关键字的类型set默认要求T支持小于比较,如果不支持或者想按自己的需求走可以自行实现仿函数传给第二个模版参数。set底层存储数据的内存是从空间配置器申请的,如果需要可以自己实现内存池,传给第三个参数。- 一般情况下,我们都不需要传后两个模版参数。
set底层是用红黑树实现,增删查效率是 O ( l o g N ) O(logN) O(logN),迭代器遍历是走的搜索树的中序,所以是有序的。- 与
vector/list等容器的使用,STL容器接口设计,高度相似,所以这里我们就不再一个接口一个接口的介绍,挑比较重要的接口进行介绍。

2.2 set的构造和迭代器
0.构造:
set 的支持正向和反向迭代遍历,遍历默认按升序顺序,因为底层是二叉搜索树,迭代器遍历走的中序;支持迭代器就意味着支持范围 for,set 的 iterator 和 const_iterator 都不支持迭代器修改数据 ,修改关键字数据,防止破坏底层搜索树的结构。

1. 空构造(empty (1))
cpp
explicit set (const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());
- 作用 :创建空的
set容器。 - 参数 :
comp:可选,自定义的键比较规则(默认使用key_compare,即<比较);alloc:可选,内存分配器(默认使用allocator_type)。
2. 范围构造(range (2))
cpp
template <class InputIterator>
set (InputIterator first, InputIterator last,
const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());
- 作用 :将迭代器
[first, last)范围内的元素插入set(自动去重并按规则排序)。 - 参数 :
first/last:输入迭代器,指定待插入元素的范围;comp/alloc:同空构造的可选参数。
3. 拷贝构造(copy (3))
cpp
set (const set& x);
- 作用 :创建一个与已有
set对象x内容完全相同的新set。
4. 初始化列表(C++11)
cpp
void test_set1()
{
set<int> s = { 5,1,5,3,4,2,6,83,9,10,22 };
// 中序,排序+去重
set<int>::iterator it = s.begin();
while (it != s.end())
{
// 普通迭代器也不支持修改
// *it = 1;
cout << *it << " ";
++it;
}
cout << endl;
}

2.3 修改器(Modifiers)的成员函数

这是C++ std::set的修改器(Modifiers)成员函数 ,负责对set的元素进行增删等操作。以下结合代码示例逐一讲解:
0. 迭代器

这个太基础了 我个人感觉实在没什么可以说的 唯一要注意的就是 不能通过迭代器修改里面的值。
1. insert:插入元素
功能 :向set中插入键值(自动去重、按规则排序)。
代码示例:
cpp
#include <set>
#include <iostream>
using namespace std;
int main() {
set<int> s;
// 插入单个元素
s.insert(3);
s.insert(1);
s.insert(2);
s.insert(2); // 重复元素,插入失败(set自动去重)
// 遍历输出:1 2 3(默认升序)
for (int val : s) cout << val << " ";
return 0;
}
2. erase:删除元素
功能 :删除set中的元素(支持按键值、迭代器、范围删除)。
代码示例:
cpp
int main() {
set<int> s = {1,2,3,4,5};
// 1. 按键值删除
s.erase(3);
// 2. 按迭代器删除
auto it = s.find(4);
if (it != s.end()) s.erase(it);
// 3. 按范围删除(删除[begin, end))
s.erase(s.begin(), s.end()); //左闭右开
cout << s.size(); // 输出0
return 0;
}
3. swap:交换两个set的内容(与算法库swap对比)
功能 :交换当前set与另一个set的所有元素(底层仅交换内部指针,效率高,而算法库 中swap则涉及深层拷贝等)。
代码示例:
cpp
int main() {
set<int> s1 = {1,2,3};
set<int> s2 = {4,5,6};
s1.swap(s2);
// s1变为{4,5,6},s2变为{1,2,3}
for (int val : s1) cout << val << " "; // 输出4 5 6
return 0;
}
4. clear:清空所有元素
功能 :删除set中的所有元素,使其变为空容器。
代码示例:
cpp
int main() {
set<int> s = {1,2,3};
s.clear();
cout << s.empty(); // 输出1(表示容器为空)
return 0;
}
5. emplace:构造并插入元素(C++11+)
功能 :直接在set中构造元素(避免临时对象拷贝,比insert更高效)。
代码示例:
cpp
int main() {
set<pair<int, string>> s;
// emplace直接构造pair(无需手动创建临时pair)
s.emplace(1, "apple");
// 等价于insert,但emplace更高效
s.insert(pair<int, string>(2, "banana"));
return 0;
}
6. emplace_hint:带位置提示的构造插入(C++11+)
功能 :在指定迭代器位置附近构造并插入元素(若位置合理,可提升插入效率)。
代码示例:
cpp
int main() {
set<int> s = {1,3,5};
// 提示在3的位置附近插入2(实际插入到1和3之间)
auto it = s.find(3);
s.emplace_hint(it, 2);
for (int val : s) cout << val << " "; // 输出1 2 3 5
return 0;
}
2.4 find(与算法库find的对比)

这是C++标准库中std::set::find成员函数的声明(支持C++98及以上版本),其核心信息与使用说明如下:
cpp
iterator find (const value_type& val) const;
- 返回值 :
iterator(set的迭代器),指向找到的键值val;若val不存在,返回set::end()(尾后迭代器)。 - 参数 :
const value_type& val,待查找的键值(value_type即set的关键字类型)。 - 特性 :
const修饰表示该函数不会修改set本身。
find是set的查找接口 ,基于底层红黑树的特性,能以 O ( log N ) O(\log N) O(logN)的时间复杂度快速定位键值,常用于判断元素是否存在、获取元素迭代器。
- 效率:由于
set底层是有序的红黑树,find通过二分查找逻辑实现,效率远高于算法库的find( O ( N ) O(N) O(N))。 - 迭代器特性:
set的迭代器是双向迭代器,且不可修改(因为修改键值会破坏set的有序性)。
cpp
#include <set>
#include <iostream>
using namespace std;
int main() {
set<int> s = {1, 2, 3, 4, 5};
// 查找键值3
auto it = s.find(3);
if (it != s.end()) {
cout << "找到元素:" << *it << endl; // 输出"找到元素:3"
}
// 查找不存在的键值6
it = s.find(6);
if (it == s.end()) {
cout << "未找到元素" << endl; // 输出"未找到元素"
}
return 0;
}
2.5 key_comp && value_comp
| 函数名 | 功能描述 |
|---|---|
key_comp |
返回set用于比较**键(key)**的函数对象,是set模板参数中指定的比较类型(默认是less<key_type>)。 |
value_comp |
功能与key_comp一致(因为set的键和值是同一类型),返回的比较对象逻辑等同于key_comp。 |
set的底层红黑树依赖比较规则维持有序性,这两个函数可以获取当前set使用的比较逻辑,常用于:
- 自定义比较规则时,验证或复用
set的排序逻辑;- 对
set的元素进行外部排序(保持与set内部一致的规则)。
代码示例
cpp
#include <set>
#include <iostream>
using namespace std;
int main() {
// 定义一个按降序排序的set
set<int, greater<int>> s = {3, 1, 2};
// 获取key_comp比较对象
auto comp = s.key_comp();
// 使用comp判断两个键的大小关系(符合set的降序规则)
bool res = comp(1, 2); // 等价于greater<int>()(1,2),结果为false
cout << "1 > 2 ? " << boolalpha << res << endl; // 输出"1 > 2 ? false"
return 0;
}
2.6 count(与find比较)
- 声明 :
size_type count (const value_type& val) const; - 功能 :统计
set中值为val的元素个数(由于set不允许重复元素,返回值只能是0或1)。 - 参数 :
const value_type& val,待统计的目标值; - 返回值 :
size_type(无符号整数类型),表示val在set中的出现次数。
由于set的"唯一性"特性,count的实际作用是判断元素是否存在 (返回1表示存在,0表示不存在),效果等价于find(val) != end(),但语义更偏向"计数"。
cpp
#include <set>
#include <iostream>
using namespace std;
int main() {
set<int> s = {1, 2, 3, 4};
// 统计存在的元素
size_t cnt1 = s.count(3);
cout << "元素3的出现次数:" << cnt1 << endl; // 输出1
// 统计不存在的元素
size_t cnt2 = s.count(5);
cout << "元素5的出现次数:" << cnt2 << endl; // 输出0
return 0;
}
与find的区别
| 函数 | 功能 | 返回值类型 | 适用场景 |
|---|---|---|---|
find |
查找元素并返回迭代器 | 迭代器 | 需要获取元素的位置时 |
count |
统计元素出现次数 | 无符号整数 | 仅需判断元素是否存在时 |
综合对比 在判断元素是否存在时 count 更加方便!
2.7 lower_bound && upper_bound
| 函数名 | 功能描述 |
|---|---|
lower_bound |
返回指向**第一个不小于(≥)目标值val**的元素的迭代器;若所有元素都小于val,返回end()。 |
upper_bound |
返回指向**第一个大于(>)目标值val**的元素的迭代器;若所有元素都不大于val,返回end()。 |
由于
set是有序容器(默认升序),这两个函数通过二分查找(时间复杂度 O ( log N ) O(\log N) O(logN))快速定位边界,常用于获取"等于val的元素区间"([lower_bound, upper_bound))。
cpp
#include <set>
#include <iostream>
using namespace std;
int main() {
set<int> s = {1, 3, 5, 7, 9};
int val = 5;
// 获取lower_bound:第一个≥5的元素(即5)
auto lb = s.lower_bound(val);
cout << "lower_bound(" << val << "): " << *lb << endl; // 输出5
// 获取upper_bound:第一个>5的元素(即7)
auto ub = s.upper_bound(val);
cout << "upper_bound(" << val << "): " << *ub << endl; // 输出7
// 若val不存在(如val=4)
val = 4;
lb = s.lower_bound(val); // 第一个≥4的元素是5
ub = s.upper_bound(val); // 第一个>4的元素是5
cout << "val=4时,[lb, ub)区间长度:" << distance(lb, ub) << endl; // 输出0(无元素)
return 0;
}
用处: 删除或者遍历 某一区间的值:
3. multiset
multiset和set,核心差异集中在元素唯一性、接口行为、使用场景 三个维度,但是multiset不用单独包含头文件,二者基本完全类似。
3.1 核心特性差异
| 维度 | set |
multiset |
|---|---|---|
| 元素唯一性 | 不允许重复元素(键唯一) | 允许重复元素(键可重复) |
| 底层实现 | 红黑树(平衡二叉搜索树) | 红黑树(平衡二叉搜索树) |
| 有序性 | 元素按键有序排列 | 元素按键有序排列 |
3.2 接口行为差异
以常用成员函数为例,两者行为因"唯一性"产生区别:
| 接口 | set的行为 |
multiset的行为 |
|---|---|---|
insert |
插入重复元素时返回失败(仅插入一次) | 插入重复元素时成功(可插入多次) |
find |
返回第一个匹配键的迭代器 | 返回第一个匹配键的迭代器(切记是中序遍历的第一个) |
count |
返回0或1(仅表示存在性) | 返回键的实际出现次数 |
lower_bound/upper_bound |
区间[lb, ub)长度最多为1 |
区间[lb, ub)包含所有匹配键的元素 |
erase |
按键删除时,删除所有匹配的元素(仅1个) | 按键删除时,删除所有匹配的元素(可能多个) |
3.3 使用场景差异
set:适用于需要唯一键的场景(如存储不重复的ID、去重后的数据集);multiset:适用于需要统计键出现次数的场景(如统计单词频率、存储可重复的有序数据)。
cpp
#include <set>
#include <iostream>
using namespace std;
int main() {
// set:键唯一
set<int> s = {1, 2, 2, 3};
cout << "set大小:" << s.size() << endl; // 输出3(自动去重)
// multiset:键可重复
multiset<int> ms = {1, 2, 2, 3};
cout << "multiset大小:" << ms.size() << endl; // 输出4(保留重复)
// count接口差异
cout << "set中2的数量:" << s.count(2) << endl; // 输出1
cout << "multiset中2的数量:" << ms.count(2) << endl; // 输出2
return 0;
}
4. 例题部分
4.1 环形链表 II
题目链接: 点此跳转
我们之前C语言阶段是使用快慢指针完成的 其实我们可以用ste<Node*> s来做 遍历链表,每个节点是否在s中,不在就插入,在的第一个点就是入口点 , 要说这道题唯一的缺陷 就是有 O ( N ) O(N) O(N)的空间复杂度。
cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode* head)
{
set<ListNode*> s;
ListNode* tmp=head;
while(tmp!=NULL)
{
if(s.count(tmp)==0)
{
s.insert(tmp);
tmp=tmp->next;
}
else
{
return tmp;
}
}
return NULL;
}
};
4.2 两个数组的交集
题目链接: 点此转跳
这题也挺简单的 其实就是拿set去重就可以了 然后用一个set的count去遍历另一个set 把重复的插入vector即可。
cpp
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2)
{
//去重
set<int> s1(nums1.begin(),nums1.end());
set<int> s2(nums2.begin(),nums2.end());
vector<int> v;
for(auto e : s1)
{
if(s2.count(e))
{
v.push_back(e);
}
}
return v;
}
};
补充 :
这么做 其实复杂度还是高的 因为
count的特性 时间复杂度可能要 O ( N ∗ l o g N ) O(N*logN) O(N∗logN)但是利用双指针特性 就可以把复杂度压缩到 O ( N ) O(N) O(N)。这个方法不光能找交集,也能找差集。




