跳到主要内容C++ Set 与 Map 底层实现及高频算法实战 | 极客日志C++算法
C++ Set 与 Map 底层实现及高频算法实战
深入讲解了 C++ STL 中 Set 和 Map 的底层实现原理,包括红黑树结构、增删查复杂度及迭代器特性。重点分析了 Set 的有序去重、Map 的键值映射及 operator[] 的行为细节,并结合四个经典 LeetCode 案例(数组交集、环形链表、随机链表复制、TopK 高频词)展示了容器在算法题中的实战应用。内容涵盖多集合处理、区间操作及自定义排序策略,旨在帮助开发者掌握数据结构核心用法并提升算法解题能力。
19510189250 浏览 Set 类的实现
Set 的声明中,T 代表底层关键字的类型。默认情况下,Set 要求类型 T 支持比较运算;如果不支持或需要自定义排序规则,可以传入仿函数作为第二个模板参数。内存方面,Set 底层从空间配置器申请,如需优化可自定义内存池。
Set 基于红黑树实现,增删查时间复杂度为 O(logN)。迭代器遍历遵循搜索树的中序遍历,因此数据天然有序(升序)。
Set 的构造和迭代器
插入结构数字时,set<int> s; 即指定了模板参数 T。Set 的核心特性是排序加去重。
#include <iostream>
#include <set>
using namespace std;
int main() {
set<int> s;
s.insert(4);
s.insert(3);
s.insert(8);
s.insert(9);
s.insert(2);
s.insert(6);
auto it = s.begin();
while (it != s.end()) {
cout << *it << " ";
++it;
}
cout << endl;
return 0;
}
若需降序,可使用 set<int, greater<int>> s;。此时比较逻辑反转,比根大的数传左边,比根小的传右边,从而形成降序序列。
Set 还支持初始化列表插入,重复值会被自动忽略:
s.insert({2, 3, 4, 5});
for (auto e : s) {
cout << e << ;
}
" "
Set: erase 和 find
删除最小值:由于 Set 按升序存储,s.erase(s.begin()) 会直接删除最小的元素。
s.erase(s.begin());
for (auto e : s) {
cout << e << " ";
}
int x;
cin >> x;
int num = s.erase(x);
if (num == 0) {
cout << x << "不存在" << endl;
} else {
cout << x << "删除成功" << endl;
}
auto pos = s.find(x);
if (pos != s.end()) {
s.erase(pos);
cout << x << "删除成功" << endl;
} else {
cout << x << "不存在" << endl;
}
注意迭代器失效问题:在方法 2 中,erase(pos) 后,pos 指向的节点已被移除,该迭代器立即失效。若后续访问 *pos 会导致野指针错误。删除根节点时虽可能通过替代法维持树结构,但原迭代器依然无效。
int x;
auto pos1 = find(s.begin(), s.end(), x);
auto pos2 = s.find(x);
Set: count
利用 count 进行存在性判断。对于 Set,返回值为 0 或 1。
cin >> x;
if (s.count(x)) {
cout << x << "在集合中" << endl;
} else {
cout << x << "不在集合中" << endl;
}
Set: lower_bound 和 upper_bound
#include <iostream>
#include <set>
using namespace std;
int main() {
set<int> mset;
for (int i = 1; i < 10; i++) {
mset.insert(i * 10);
}
auto itlow = mset.lower_bound(30);
auto itup = mset.upper_bound(50);
mset.erase(itlow, itup);
for (auto e : mset) {
cout << e << " ";
}
return 0;
}
Multiset 和 Set
multiset 与 set 用法基本一致,核心区别在于是否去重。Multiset 允许键值重复,仅保证有序。
int main() {
multiset<int> s = {4, 2, 3, 1, 5, 6, 8, 6, 43, 2, 5};
auto it = s.begin();
while (it != s.end()) {
cout << *it << " ";
++it;
}
cout << endl;
int x;
cin >> x;
auto pos = s.find(x);
while (pos != s.end() && *pos == x) {
cout << *pos << " ";
++pos;
}
cout << endl;
return 0;
}
注意:find 在 Multiset 中只返回中序遍历的第一个匹配项。若要处理所有重复项,需配合循环 ++ 迭代器。
Map: insert
insert 返回 pair<iterator, bool>。展开来看,value_type 是 pair<const key_type, mapped_type>。
- 插入成功:返回
{新插入值的迭代器,true}。
- 插入失败(Key 已存在):返回
{已存在 Key 的迭代器,false}。
pair<iterator, bool> ret = map.insert({key, value});
if (ret.second) {
} else {
}
Map: operator[]
operator[] 内部调用了 insert,并返回映射值的引用。
mapped_type& operator[](const key_type& k) {
pair<iterator, bool> ret = insert({k, mapped_type()});
iterator it = ret.first;
return it->second;
}
- 若 Key 不存在:插入
{k, mapped_type()}(默认构造值),返回该值的引用。适合计数场景。
- 若 Key 存在:不插入,直接返回现有值的引用。适合修改场景。
int main() {
string myarray[] = {"秋", "冬", "夏", "春", "夏", "秋", "夏", "春", "夏", "秋", "秋", "冬"};
map<string, int> countMap;
for (const auto& e : myarray) {
countMap[e]++;
}
for (const auto& it : countMap) {
cout << it.first << ":" << it.second << endl;
}
return 0;
}
总结:operator[] 既可用于插入新键值对,也可用于更新已有键的值。对于计数统计非常方便。
Multimap 和 Map 的差异
- Key 冗余:Multimap 支持 Key 重复,Map 不支持。
- 操作差异:Multimap 的
find 返回第一个匹配项;operator[] 在 Multimap 中不可用(因为无法确定返回哪个 Key 对应的 Value),仅支持 insert。
力扣题目实战
两个数组的交集
思路:利用 Set 的去重和有序特性。将两个数组转为 Set,使用双指针遍历。
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
vector<int> ret;
set<int> s1(nums1.begin(), nums1.end());
set<int> s2(nums2.begin(), nums2.end());
auto it1 = s1.begin();
auto it2 = s2.begin();
while (it1 != s1.end() && it2 != s2.end()) {
if (*it1 < *it2) {
++it1;
} else if (*it1 > *it2) {
++it2;
} else {
ret.push_back(*it1);
++it1;
++it2;
}
}
return ret;
}
};
要点:相等则加入结果并双指针前进;较小的一方前进以寻找更大匹配值。
环形链表
思路:利用 Set 的唯一性检测节点是否重复访问。
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
set<ListNode*> s;
ListNode* cur = head;
while (cur) {
if (s.count(cur)) {
return cur;
} else {
s.insert(cur);
cur = cur->next;
}
}
return nullptr;
}
};
注意:count 返回 1 表示存在,0 表示不存在。此解法空间复杂度为 O(N)。
随机链表的复制
思路:深拷贝需同时处理 next 和 random 指针。使用 Map 建立原节点与新节点的映射关系。
class Solution {
public:
Node* copyRandomList(Node* head) {
map<Node*, Node*> randomMap;
Node* copyhead = nullptr, *copytail = nullptr;
Node* ptr = head;
while (ptr) {
if (!copytail) {
copytail = copyhead = new Node(ptr->val);
} else {
copytail->next = new Node(ptr->val);
copytail = copytail->next;
}
randomMap[ptr] = copytail;
ptr = ptr->next;
}
Node* copy = copyhead;
ptr = head;
while (ptr) {
if (ptr->random) {
copy->random = randomMap[ptr->random];
} else {
copy->random = nullptr;
}
copy = copy->next;
ptr = ptr->next;
}
return copyhead;
}
};
前 K 个高频单词
思路:统计频率 -> 转换为 Vector -> 自定义排序。
class Solution {
public:
struct kvFunction {
bool operator()(const pair<string, int>& w1, const pair<string, int>& w2) {
return w1.second > w2.second;
}
};
vector<string> topKFrequent(vector<string>& words, int k) {
map<string, int> countMap;
for (auto& it : words) {
countMap[it]++;
}
vector<pair<string, int>> v(countMap.begin(), countMap.end());
stable_sort(v.begin(), v.end(), kvFunction());
vector<string> ret;
for (int i = 0; i < k; i++) {
ret.push_back(v[i].first);
}
return ret;
}
};
map 天然按键字典序升序存储。
stable_sort 保证频率相同时保持原有顺序(即字典序)。
- 组合起来实现了'频率降序,同频字典序升序'的需求。
Set 和 Map 构造对比
| 特性 | Set | Map |
|---|
| 遍历顺序 | 升序(中序遍历) | Key 升序(中序遍历) |
| 迭代器修改 | 不支持修改 key(破坏树结构) | 不支持修改 key,支持修改 value |
| 底层结构 | 红黑树 | 红黑树 |
两者均支持正向和反向迭代,且都依赖红黑树维持平衡与有序性。在实际开发中,理解其底层机制有助于选择合适的数据结构并规避迭代器失效等陷阱。
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online