当前位置: 首页 > news >正文

C++进阶:(四)set系列容器的全面指南

目录

前言

一、容器分类核心:序列式容器与关联式容器的本质区别

1.1 序列式容器:按存储位置有序访问

1.2 关联式容器:按关键字有序访问

二、set 系列容器底层实现与核心特性

2.1 红黑树:set 系列的底层基石

2.2 set 容器的核心特性

2.3 multiset 容器的核心特性

三、set 系列容器核心接口详解与实战示例

3.1 构造函数与初始化

3.1.1 构造接口

3.1.2 示例:多种初始化方式

3.2 迭代器与遍历

3.2.1 迭代器接口

3.2.2 示例:多种遍历方式

3.3 插入操作(insert)

3.3.1 插入接口

3.3.2 接口细节说明

3.3.3 示例:插入操作

3.4 查找操作(find/count/lower_bound/upper_bound)

3.4.1 查找接口

3.4.2 接口差异说明(set vs multiset)

3.4.3 示例:查找操作

3.5 删除操作(erase)

3.5.1 删除接口

3.5.2 接口差异说明(set vs multiset)

3.5.3 示例:删除操作

四、set 与 multiset 的核心差异总结

五、set 系列容器的实战场景与 LeetCode 例题解析

5.1 场景一:数组交集(去重 + 有序特性)

5.2 场景二:环形链表检测(快速查找特性)

5.3 场景三:统计元素出现次数(multiset 的冗余特性)

六、set 系列容器的使用注意事项与性能优化

6.1 使用注意事项

6.2 性能优化技巧

总结


前言

        在 C++ 编程中,STL(Standard Template Library)容器是提升开发效率的核心工具之一。容器作为数据存储的载体,根据底层实现和逻辑结构的不同,主要分为序列式容器和关联式容器两大类。其中 set 系列作为关联式容器的重要成员,凭借其有序性、去重特性和高效的增删查操作,在实际开发中应用广泛。本文将从容器分类的本质区别入手,深入剖析序列式容器与关联式容器的核心特性,再全面详解 set 系列(set、multiset)的底层实现、接口使用、核心差异及实战场景。下面就让我们正式开始吧!


一、容器分类核心:序列式容器与关联式容器的本质区别

        STL 容器的分类并非随意划分,而是基于数据的逻辑结构、存储方式和访问规则的本质差异。理解这两类容器的核心区别,是后续灵活选择容器的关键。

1.1 序列式容器:按存储位置有序访问

        序列式容器是我们接触最早的 STL 容器类型,常见的包括 string、vector、list、deque、array、forward_list 等。其核心特征是围绕 “线性序列” “位置依赖” 展开的:

  • 逻辑结构:数据以线性序列的形式组织,每个元素的位置由其插入顺序决定,元素之间仅存在 “前后相邻” 的关系,没有额外的关联约束。
  • 存储与访问规则:元素按插入时的存储位置顺序保存和访问,访问方式依赖于索引(如 vector、array)或迭代器遍历(如 list、forward_list)。
  • 核心特性:元素的 “位置” 是其唯一的标识,交换任意两个元素的位置后,容器的逻辑结构不会被破坏,仅元素的顺序发生改变。
  • 效率特点
    • 随机访问效率:vector、array 支持 O (1) 时间复杂度的随机访问,forward_list、list 不支持随机访问。
    • 增删效率:vector 在尾部增删效率 O (1),中间插入删除效率 O (N);list 在任意位置增删效率 O (1),但需遍历找到目标位置。

1.2 关联式容器:按关键字有序访问

        关联式容器是 STL 中用于高效查找场景的容器,主要包括 map/set 系列和 unordered_map/unordered_set 系列。其核心特征围绕 “关键字关联” “有序性” 展开:

  • 逻辑结构:底层通常基于非线性结构(如红黑树)实现,元素之间通过 “关键字” 建立紧密关联,而非依赖存储位置。
  • 存储与访问规则:元素按关键字的大小关系有序存储,访问时无需依赖位置索引,而是通过关键字直接查找。
  • 核心特性:关键字是元素的核心标识,交换两个元素的位置会破坏底层非线性结构的有序性,导致容器功能异常。
  • 效率特点:增删查操作的时间复杂度均为 O (log N),源于底层红黑树的平衡特性,确保查找路径长度稳定。
  • 两大系列差异
    • map/set 系列:底层基于红黑树实现,元素按关键字有序排列,支持范围查找(如 lower_bound、upper_bound)。
    • unordered_map/unordered_set 系列:底层基于哈希表实现,元素无序排列,增删查平均效率 O (1),但最坏情况 O (N)。

二、set 系列容器底层实现与核心特性

        set 系列容器是关联式容器中专注于 “关键字查找” 场景的实现,包括 set 和 multiset 两个核心成员。它们的底层均基于红黑树(平衡二叉搜索树)实现,因此具备有序性和高效的增删查能力,核心差异仅在于是否支持关键字冗余。

        在这为大家提供了set和multiset的参考文档:https://legacy.cplusplus.com/reference/set/

2.1 红黑树:set 系列的底层基石

        红黑树是一种自平衡的二叉搜索树,其核心特性确保了树的高度始终保持在 O (log N) 级别,从而为 set 系列提供稳定的 O (log N) 时间复杂度操作:

  • 红黑树的 5 大规则:
    1. 每个节点要么是红色,要么是黑色。
    2. 根节点是黑色。
    3. 所有叶子节点(NIL 节点)是黑色。
    4. 如果一个节点是红色,其两个子节点必须是黑色。
    5. 从任意节点到其所有后代叶子节点的路径上,黑色节点的数量相同。

        这些规则保证了红黑树不会出现极端不平衡的情况,每次插入、删除操作后,通过旋转和颜色调整维持平衡,确保查找、插入、删除操作的时间复杂度稳定在 O (log N)。后续还会为大家详细介绍红黑树的底层原理和实现。

2.2 set 容器的核心特性

        set 容器的定位是 “有序去重的关键字集合”,其特性完全由底层红黑树和自身设计规则决定:

  • 关键字唯一:set 中不允许存在重复的关键字,插入已存在的关键字会失败。
  • 有序性:元素按关键字的升序(默认)排列,遍历顺序为红黑树的中序遍历结果。
  • 迭代器特性
    • 支持双向迭代器(iterator、reverse_iterator),可正向和反向遍历。
    • iterator 和 const_iterator 均为只读迭代器,不允许通过迭代器修改元素(修改会破坏红黑树的有序性)。
  • 模板参数
    template <class T,  // 关键字类型(同时也是元素类型)class Compare = less<T>,  // 比较仿函数(默认升序)class Alloc = allocator<T>  // 空间配置器(默认使用STL提供的alloc)> class set;
    
    • T:set 的关键字类型,也是容器中存储的元素类型(set 中 key_type 与 value_type 相同);
    • Compare:用于定义关键字的比较规则,默认使用 less<T>(升序),可自定义仿函数实现降序或自定义比较逻辑;
    • Alloc:空间配置器,负责内存分配与释放,默认情况下无需手动指定。

2.3 multiset 容器的核心特性

        multiset 与 set 的底层实现完全一致(均为红黑树),核心差异仅在于支持关键字冗余

  • 关键字可重复:multiset 允许插入多个相同的关键字,容器会保留所有重复元素并维持有序。
  • 迭代器特性:与 set 一致,支持双向迭代器,且迭代器只读。
  • 模板参数:与 set 完全相同,无需额外配置即可支持关键字冗余。
  • 核心限制:由于支持关键字重复,部分接口的行为与 set 存在差异(如 find、count、erase),后续将详细说明。

三、set 系列容器核心接口详解与实战示例

        STL 容器的接口设计具有高度一致性,set 系列的接口与 vector、list 等序列式容器有诸多相似之处,但也存在因底层红黑树特性导致的独特接口。本节将重点讲解 set 和 multiset 的核心接口(构造、迭代器、增删查),并结合实战示例说明使用场景。

3.1 构造函数与初始化

        set 和 multiset 的构造函数完全一致,支持 4 种常见的初始化方式:

3.1.1 构造接口

// 1. 无参构造:创建空set
explicit set (const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type());// 2. 迭代器区间构造:用[first, last)区间的元素初始化
template <class InputIterator>
set (InputIterator first, InputIterator last,const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type());// 3. 拷贝构造:用另一个set对象初始化
set (const set& x);// 4. 初始化列表构造:用初始化列表中的元素初始化
set (initializer_list<value_type> il,const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type());

3.1.2 示例:多种初始化方式

#include <iostream>
#include <set>
#include <vector>
using namespace std;int main() {// 1. 无参构造set<int> s1;// 2. 初始化列表构造(最常用)set<int> s2 = {4, 2, 7, 2, 8, 5};  // 自动去重,结果:2,4,5,7,8cout << "s2初始化结果:";for (auto e : s2) cout << e << " ";  // 输出:2 4 5 7 8cout << endl;// 3. 迭代器区间构造(用vector的元素初始化)vector<int> vec = {9, 3, 6, 3, 1};set<int> s3(vec.begin(), vec.end());  // 去重后:1,3,6,9cout << "s3初始化结果:";for (auto e : s3) cout << e << " ";  // 输出:1 3 6 9cout << endl;// 4. 拷贝构造set<int> s4(s3);cout << "s4拷贝构造结果:";for (auto e : s4) cout << e << " ";  // 输出:1 3 6 9cout << endl;// 5. 自定义比较规则(降序)set<int, greater<int>> s5 = {4, 2, 7, 2, 8, 5};  // 去重+降序:8,7,5,4,2cout << "s5降序初始化结果:";for (auto e : s5) cout << e << " ";  // 输出:8 7 5 4 2cout << endl;return 0;
}

3.2 迭代器与遍历

        set 系列支持双向迭代器,遍历方式包括迭代器遍历和范围 for 遍历(C++11 及以上),遍历顺序由比较仿函数决定(默认升序)。

3.2.1 迭代器接口

// 正向迭代器:指向第一个元素,遍历至end()(尾后迭代器)
iterator begin();
const_iterator begin() const;// 正向尾后迭代器:不指向任何元素,作为遍历结束标志
iterator end();
const_iterator end() const;// 反向迭代器:指向最后一个元素,遍历至rend()(反向尾后迭代器)
reverse_iterator rbegin();
const_reverse_iterator rbegin() const;// 反向尾后迭代器:作为反向遍历结束标志
reverse_iterator rend();
const_reverse_iterator rend() const;

3.2.2 示例:多种遍历方式

#include <iostream>
#include <set>
using namespace std;int main() {set<string> strSet = {"sort", "insert", "add", "erase", "find"};  // 按ASCII码升序排列// 1. 正向迭代器遍历cout << "正向迭代器遍历:";set<string>::iterator it = strSet.begin();while (it != strSet.end()) {// *it = "modify";  // 错误:迭代器只读,不允许修改cout << *it << " ";  // 输出:add erase find insert sort(ASCII码升序)++it;}cout << endl;// 2. 反向迭代器遍历cout << "反向迭代器遍历:";set<string>::reverse_iterator rit = strSet.rbegin();while (rit != strSet.rend()) {cout << *rit << " ";  // 输出:sort insert find erase add(反向升序=降序)++rit;}cout << endl;// 3. 范围for遍历(最简洁,推荐使用)cout << "范围for遍历:";for (const auto& e : strSet) {  // 用const auto&避免拷贝,提高效率cout << e << " ";  // 输出:add erase find insert sort}cout << endl;return 0;
}

注意事项

  • set 的迭代器是只读的,无论使用 iterator 还是 const_iterator,都不能修改元素的值,否则会破坏红黑树的有序性,导致容器行为异常。
  • 遍历顺序由比较仿函数决定,默认使用 less<T>,按关键字升序排列;若使用 greater<T>,则按降序排列。

3.3 插入操作(insert)

        插入操作是 set 系列的核心接口之一,负责向红黑树中添加元素,同时维持容器的有序性和去重特性(set)或冗余特性(multiset)。

3.3.1 插入接口

// 1. 插入单个元素:返回pair<iterator, bool>
pair<iterator, bool> insert (const value_type& val);// 2. 插入初始化列表中的元素
void insert (initializer_list<value_type> il);// 3. 插入[first, last)区间的元素
template <class InputIterator>
void insert (InputIterator first, InputIterator last);

3.3.2 接口细节说明

  • 单个元素插入(set)
    • 返回值为pair<iterator, bool>,其中:
      • first:指向插入的元素(若插入成功)或已存在的元素(若插入失败)的迭代器。
      • second:布尔值,true 表示插入成功(元素不存在),false 表示插入失败(元素已存在)。
  • 单个元素插入(multiset)
    • 无返回值(或返回 iterator,C++11 后统一为 iterator),因为支持关键字冗余,插入一定成功。
  • 批量插入(初始化列表 / 迭代器区间)
    • set 会自动过滤重复元素,仅插入不存在的元素。
    • multiset 会插入所有元素,包括重复元素。

3.3.3 示例:插入操作

#include <iostream>
#include <set>
#include <vector>
using namespace std;int main() {// 一、set插入示例(去重)set<int> s1;// 1. 插入单个元素auto ret1 = s1.insert(5);cout << "插入5:" << (ret1.second ? "成功" : "失败") << ",元素位置:" << *ret1.first << endl;  // 成功,5auto ret2 = s1.insert(5);  // 插入重复元素cout << "插入5:" << (ret2.second ? "成功" : "失败") << ",元素位置:" << *ret2.first << endl;  // 失败,5// 2. 插入初始化列表s1.insert({2, 7, 3, 2});  // 过滤重复的2,插入2、7、3cout << "插入列表后s1:";for (auto e : s1) cout << e << " ";  // 输出:2 3 5 7cout << endl;// 3. 插入迭代器区间vector<int> vec = {4, 6, 3, 8};s1.insert(vec.begin(), vec.end());  // 过滤重复的3,插入4、6、8cout << "插入vector后s1:";for (auto e : s1) cout << e << " ";  // 输出:2 3 4 5 6 7 8cout << endl;// 二、multiset插入示例(允许重复)multiset<int> ms1;// 1. 插入单个元素(重复插入)ms1.insert(5);ms1.insert(5);ms1.insert(5);cout << "multiset插入3个5后:";for (auto e : ms1) cout << e << " ";  // 输出:5 5 5cout << endl;// 2. 插入初始化列表(含重复元素)ms1.insert({2, 5, 3, 2});  // 插入所有元素,包括重复的2和5cout << "插入列表后ms1:";for (auto e : ms1) cout << e << " ";  // 输出:2 2 3 5 5 5 5cout << endl;return 0;
}

3.4 查找操作(find/count/lower_bound/upper_bound)

        查找是关联式容器的核心优势,set 系列提供了多个高效的查找接口,满足不同场景的需求。

3.4.1 查找接口

// 1. 查找关键字val:返回指向val的迭代器,未找到返回end()
iterator find (const value_type& val);
const_iterator find (const value_type& val) const;// 2. 统计关键字val的个数:set返回0或1,multiset返回实际个数
size_type count (const value_type& val) const;// 3. 查找第一个>=val的元素:返回其迭代器
iterator lower_bound (const value_type& val) const;
const_iterator lower_bound (const value_type& val) const;// 4. 查找第一个>val的元素:返回其迭代器
iterator upper_bound (const value_type& val) const;
const_iterator upper_bound (const value_type& val) const;

3.4.2 接口差异说明(set vs multiset)

  • find
    • set:若找到,返回唯一对应元素的迭代器;未找到返回 end ()。
    • multiset:若存在多个相同元素,返回中序遍历的第一个元素的迭代器。
  • count
    • set:仅用于判断元素是否存在(返回 0 或 1),效率与 find 一致(O (log N))。
    • multiset:返回元素的实际个数,需遍历所有相同元素,效率 O (log N + k)(k 为元素个数)。
  • lower_bound/upper_bound
    • 两者在 set 和 multiset 中行为一致,用于范围查找。

3.4.3 示例:查找操作

#include <iostream>
#include <set>
using namespace std;int main() {// 一、set查找示例set<int> s1 = {2, 3, 4, 5, 6, 7, 8};// 1. find查找auto pos1 = s1.find(5);if (pos1 != s1.end()) {cout << "找到元素5,位置:" << *pos1 << endl;  // 输出:5} else {cout << "未找到元素5" << endl;}auto pos2 = s1.find(9);if (pos2 != s1.end()) {cout << "找到元素9" << endl;} else {cout << "未找到元素9" << endl;  // 输出}// 2. count统计cout << "元素5的个数:" << s1.count(5) << endl;  // 输出:1cout << "元素9的个数:" << s1.count(9) << endl;  // 输出:0// 3. lower_bound/upper_bound范围查找auto itLow = s1.lower_bound(3);  // 第一个>=3的元素:3auto itUp = s1.upper_bound(6);   // 第一个>6的元素:7cout << "范围[3,6]的元素:";for (auto it = itLow; it != itUp; ++it) {cout << *it << " ";  // 输出:3 4 5 6}cout << endl;// 二、multiset查找示例multiset<int> ms1 = {2, 2, 3, 5, 5, 5, 5};// 1. find查找(返回第一个匹配元素)auto pos3 = ms1.find(5);if (pos3 != ms1.end()) {cout << "multiset中第一个5的位置:" << *pos3 << endl;  // 输出:5// 遍历所有5cout << "multiset中所有5:";while (pos3 != ms1.end() && *pos3 == 5) {cout << *pos3 << " ";  // 输出:5 5 5 5++pos3;}cout << endl;}// 2. count统计cout << "multiset中元素5的个数:" << ms1.count(5) << endl;  // 输出:4cout << "multiset中元素2的个数:" << ms1.count(2) << endl;  // 输出:2return 0;
}

3.5 删除操作(erase)

        删除操作用于移除容器中的元素,支持按元素值、迭代器位置或迭代器区间删除,set 和 multiset 的接口一致,但行为存在差异。

3.5.1 删除接口

// 1. 按迭代器位置删除:返回删除元素的下一个元素的迭代器
iterator erase (const_iterator position);// 2. 按元素值删除:返回删除的元素个数(set返回0或1,multiset返回实际删除个数)
size_type erase (const value_type& val);// 3. 按迭代器区间删除:返回删除区间的下一个元素的迭代器
iterator erase (const_iterator first, const_iterator last);

3.5.2 接口差异说明(set vs multiset)

  • 按元素值删除
    • set:最多删除 1 个元素(若存在),返回 1;不存在返回 0。
    • multiset:删除所有与 val 相等的元素,返回删除的元素个数。
  • 按迭代器删除
    • 两者行为一致,仅删除迭代器指向的单个元素,返回下一个元素的迭代器。
  • 按区间删除
    • 两者行为一致,删除 [first, last) 区间内的所有元素,返回 last 迭代器。

3.5.3 示例:删除操作

#include <iostream>
#include <set>
using namespace std;int main() {// 一、set删除示例set<int> s1 = {2, 3, 4, 5, 6, 7, 8};// 1. 按迭代器位置删除(删除第一个元素)auto pos1 = s1.begin();s1.erase(pos1);cout << "删除第一个元素后s1:";for (auto e : s1) cout << e << " ";  // 输出:3 4 5 6 7 8cout << endl;// 2. 按元素值删除(删除5)size_t num1 = s1.erase(5);cout << "删除元素5:" << (num1 ? "成功" : "失败") << ",删除个数:" << num1 << endl;  // 成功,1cout << "删除后s1:";for (auto e : s1) cout << e << " ";  // 输出:3 4 6 7 8cout << endl;// 3. 按区间删除(删除[4,6])auto itLow = s1.lower_bound(4);auto itUp = s1.upper_bound(6);s1.erase(itLow, itUp);cout << "删除区间[4,6]后s1:";for (auto e : s1) cout << e << " ";  // 输出:3 7 8cout << endl;// 二、multiset删除示例multiset<int> ms1 = {2, 2, 3, 5, 5, 5, 5};// 1. 按元素值删除(删除所有5)size_t num2 = ms1.erase(5);cout << "multiset删除所有5,删除个数:" << num2 << endl;  // 输出:4cout << "删除后ms1:";for (auto e : ms1) cout << e << " ";  // 输出:2 2 3cout << endl;// 2. 按迭代器位置删除(删除第一个2)auto pos2 = ms1.find(2);if (pos2 != ms1.end()) {ms1.erase(pos2);}cout << "删除第一个2后ms1:";for (auto e : ms1) cout << e << " ";  // 输出:2 3cout << endl;return 0;
}

注意事项

  • 删除迭代器时,需确保迭代器有效(不为 end ()),否则会导致未定义行为。
  • 按元素值删除时,set 仅删除一个元素multiset 删除所有相同元素,大家需要根据实际需求进行选择。

四、set 与 multiset 的核心差异总结

        set 和 multiset 的底层实现、大部分接口完全一致,但因是否支持关键字冗余,导致部分接口行为和使用场景存在差异。以下是核心差异的详细对比:

对比维度setmultiset
关键字特性关键字唯一,不允许重复关键字可重复,支持冗余
insert 返回值pair<iterator, bool>,标识插入成功与否iterator(C++11 后),插入必定成功
find 行为返回唯一匹配元素的迭代器,未找到返回 end ()返回中序遍历的第一个匹配元素的迭代器
count 行为返回 0 或 1,仅用于判断元素是否存在返回匹配元素的实际个数
erase(按值)删除最多 1 个元素,返回 0 或 1删除所有匹配元素,返回删除个数
适用场景去重 + 有序存储、快速查找唯一元素允许重复 + 有序存储、统计元素出现次数
  • 若需存储唯一元素并快速查找,优先使用 set
  • 若需存储重复元素并维持有序,或需要统计元素出现次数,使用 multiset

五、set 系列容器的实战场景与 LeetCode 例题解析

        set 系列凭借其有序性、去重特性和高效的增删查操作,在实际开发和算法题中有着广泛的应用。本节将结合经典 LeetCode 例题,讲解 set 系列的实战用法。

5.1 场景一:数组交集(去重 + 有序特性)

题目链接:https://leetcode.cn/problems/intersection-of-two-arrays/description/

题目描述:给定两个数组 nums1 和 nums2,返回它们的交集。输出结果中的每个元素一定是唯一的。我们可以不考虑输出结果的顺序。

解题思路

  • 利用 set 的去重特性,将两个数组分别转换为 set,自动过滤重复元素。
  • 利用 set 的有序特性,通过双指针遍历两个 set,高效查找共同元素(类似归并排序的合并过程)。

代码实现

#include <iostream>
#include <vector>
#include <set>
using namespace std;class Solution {
public:vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {// 转换为set,去重并有序set<int> s1(nums1.begin(), nums1.end());set<int> s2(nums2.begin(), nums2.end());vector<int> result;auto it1 = s1.begin();auto it2 = s2.begin();// 双指针遍历,查找交集while (it1 != s1.end() && it2 != s2.end()) {if (*it1 < *it2) {++it1;  // s1的元素更小,移动s1指针} else if (*it1 > *it2) {++it2;  // s2的元素更小,移动s2指针} else {// 找到交集元素,加入结果集result.push_back(*it1);++it1;++it2;}}return result;}
};// 测试代码
int main() {Solution sol;vector<int> nums1 = {1, 2, 2, 1};vector<int> nums2 = {2, 2};vector<int> res = sol.intersection(nums1, nums2);cout << "交集结果:";for (auto e : res) cout << e << " ";  // 输出:2cout << endl;return 0;
}

复杂度分析

  • 时间复杂度:O (m log m + n log n),其中 m 和 n 分别为两个数组的长度。转换为 set 的时间复杂度为 O (m log m + n log n),双指针遍历的时间复杂度为 O (m + n),整体由排序时间主导。
  • 空间复杂度:O (m + n),用于存储两个 set。

5.2 场景二:环形链表检测(快速查找特性)

题目链接:https://leetcode.cn/problems/linked-list-cycle-ii/description/

题目描述:给定一个链表的头节点 head,返回链表开始入环的第一个节点。如果链表无环,则返回 null。

解题思路

  • 利用 set 的快速查找特性,遍历链表时将节点指针存入 set。
  • 若当前节点已存在于 set 中,说明该节点是环的入口(首次重复出现的节点)。
  • 若遍历至链表尾部(null)仍未发现重复节点,则链表无环。

代码实现

#include <iostream>
#include <set>
using namespace std;// 链表节点定义
struct ListNode {int val;ListNode *next;ListNode(int x) : val(x), next(nullptr) {}
};class Solution {
public:ListNode *detectCycle(ListNode *head) {set<ListNode*> nodeSet;ListNode *cur = head;while (cur != nullptr) {// 尝试插入当前节点,若插入失败(已存在),则为环入口auto ret = nodeSet.insert(cur);if (!ret.second) {return cur;}cur = cur->next;}// 遍历结束未发现环return nullptr;}
};// 测试代码(创建带环链表并检测)
int main() {Solution sol;// 创建链表:1 -> 2 -> 3 -> 4 -> 2(环入口为2)ListNode *head = new ListNode(1);head->next = new ListNode(2);head->next->next = new ListNode(3);head->next->next->next = new ListNode(4);head->next->next->next->next = head->next;  // 4指向2,形成环ListNode *cycleEntry = sol.detectCycle(head);if (cycleEntry != nullptr) {cout << "环的入口节点值:" << cycleEntry->val << endl;  // 输出:2} else {cout << "链表无环" << endl;}// 释放内存(简化处理)delete head->next->next->next;delete head->next->next;delete head->next;delete head;return 0;
}

5.3 场景三:统计元素出现次数(multiset 的冗余特性)

题目描述:给定一个字符串数组,统计每个字符串出现的次数,并按出现次数降序排列;若次数相同,按字符串字典序升序排列。

解题思路

  • 利用 multiset 的冗余特性,插入所有字符串,自动维持字典序。
  • 遍历 multiset,统计每个字符串的出现次数(利用 count 接口)。
  • 按出现次数和字典序排序,输出结果。

代码实现

#include <iostream>
#include <vector>
#include <set>
#include <algorithm>
#include <string>
using namespace std;// 自定义排序规则:先按次数降序,再按字典序升序
struct Compare {bool operator()(const pair<string, int>& a, const pair<string, int>& b) const {if (a.second != b.second) {return a.second > b.second;  // 次数降序} else {return a.first < b.first;  // 字典序升序}}
};vector<pair<string, int>> countAndSort(vector<string>& words) {// 用multiset存储所有单词,维持字典序multiset<string> wordSet(words.begin(), words.end());vector<pair<string, int>> result;// 遍历multiset,统计每个单词的出现次数auto it = wordSet.begin();while (it != wordSet.end()) {string word = *it;int count = wordSet.count(word);  // 统计次数result.emplace_back(word, count);// 跳过当前单词的所有重复项it = wordSet.upper_bound(word);}// 按自定义规则排序sort(result.begin(), result.end(), Compare());return result;
}// 测试代码
int main() {vector<string> words = {"apple", "banana", "apple", "orange", "banana", "apple", "pear"};auto res = countAndSort(words);cout << "单词统计结果(按次数降序、字典序升序):" << endl;for (const auto& p : res) {cout << p.first << ": " << p.second << "次" << endl;}/* 输出:apple: 3次banana: 2次orange: 1次pear: 1次*/return 0;
}

复杂度分析

  • 时间复杂度:O (n log n + m log m),其中 n 为单词总数(multiset 插入时间),m 为不同单词的个数(排序时间)。
  • 空间复杂度:O (n),用于存储 multiset 和结果集。

六、set 系列容器的使用注意事项与性能优化

6.1 使用注意事项

  1. 迭代器只读特性:set 和 multiset 的迭代器是只读的,不能通过迭代器修改元素的值,否则会破坏红黑树的有序性,导致容器行为异常。
  2. 关键字类型的要求
    • 关键字类型必须支持比较仿函数的操作(默认是 less<T>,即支持 < 运算符)。
    • 若自定义关键字类型(如结构体),需重载 < 运算符或自定义比较仿函数,否则会编译报错。
  3. 插入重复元素的处理
    • set 插入重复元素会失败,需通过 insert 的返回值判断插入结果。
    • multiset 插入重复元素会成功,若需去重需手动处理。
  4. erase 接口的差异
    • 按值删除时,set 仅删除一个元素,multiset 删除所有相同元素,需根据需求选择。
    • 按迭代器删除时,需确保迭代器有效(不为 end ()),否则会导致未定义行为。

6.2 性能优化技巧

  1. 避免频繁插入删除:红黑树的插入删除会涉及旋转和颜色调整,频繁操作会影响性能。若需批量插入,优先使用迭代器区间插入(insert (first, last)),效率高于多次单个插入。
  2. 合理选择比较仿函数:默认的 less<T>已满足大部分场景,无需自定义。若需降序,直接使用 greater<T>(STL 内置),无需手动实现。
  3. 使用 const_iterator 和 const 引用:遍历容器时,使用 const_iterator const auto & 可避免不必要的拷贝,提高效率。
  4. 提前预留空间(无直接接口):set 系列没有 reserve 接口(红黑树的空间分配由节点决定),若已知元素数量,可通过迭代器区间插入减少内存分配次数。
  5. 优先使用容器自带的 find 接口:STL 算法库的 find 函数(std::find)对 set 系列的查找效率为 O (N),而容器自带的 find 接口效率为 O (log N),需优先使用容器自带接口。

        示例:避免使用 std::find,优先使用 set::find

#include <iostream>
#include <set>
#include <algorithm>  // std::find
using namespace std;int main() {set<int> s = {2, 3, 4, 5, 6};// 不推荐:std::find,O(N)效率auto it1 = find(s.begin(), s.end(), 4);if (it1 != s.end()) {cout << "std::find找到4" << endl;}// 推荐:set::find,O(log N)效率auto it2 = s.find(4);if (it2 != s.end()) {cout << "set::find找到4" << endl;}return 0;
}

总结

        STL 容器是 C++ 编程的核心工具,熟练掌握 set 系列的使用,能大幅提升代码的效率和可读性。在实际开发中,大家应根据数据的特性(是否有序、是否重复、访问频率)选择合适的容器,让工具为需求服务。感谢大家的支持!

http://www.dtcms.com/a/569094.html

相关文章:

  • 【Java零碎知识点】----- java.util.Random 与 Math.random()
  • 补充内容:YOLOv5损失函数解析+代码阅读
  • 北仑网站建设培训学校游戏开发需要什么学历
  • 高端装备制造提速,紧固件标准化与智能化升级成为行业新焦点
  • 6项提高电机制造质量的电气测试方案
  • 09_FastMCP 2.x 中文文档之FastMCP高级功能服务器组成详解
  • 工业之“眼”的进化:基于MEMS扫描的主动式3D视觉如何驱动柔性制造
  • 基于管理会计的制造企业运营优化虚拟仿真实验
  • 工业制造领域的ODM、OEM、EMS、JDM、CM、OBM都是啥
  • 建设网站要用什么软件.net程序员网站开发工程师
  • day07(11.4)——leetcode面试经典150
  • java源代码、字节码、jvm、jit、aot的关系
  • JVM 垃圾收集器介绍
  • springcloud:理解springsecurity安全架构与认证链路(二)RBAC 权限模型与数据库设计
  • 自适应网站建设电话网站dns错误
  • 上海网站建设上海迈歌玉树营销网站建设哪家好
  • [5-01-01].第03节:JVM启航 - JVM架构
  • 2024CISCN ezjava复现
  • Cursor 项目实战:AI播客策划助手(二)—— 多轮交互打磨播客文案的技术实现与实践
  • JavaScript的Web APIs 入门到实战(day2):事件监听与交互实现,轻松实现网页交互效果(附练习巩固)
  • 网站建设难么深圳网站制作服
  • 使用vue Template version: 1.3.1时, 设置的env无法正常读取
  • HOT100题打卡第28天——位运算
  • EasyOCR的模型放在了哪里
  • 18、【Ubuntu】【远程开发】技术方案分析:私网ip掩码
  • 做购物网站哪个cms好用企业支付的网站开发费如何入帐
  • 怎样将自己做的网站给别人看微信小程序网站建设
  • 【软考】信息系统项目管理师-质量管理论文范文
  • (T24) 跨时钟域SI->Q path的latch选型
  • 学习记录记录记录记录