跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

C++ STL 关联式容器详解:map 与 set 使用

C++ STL 关联式容器主要包括 map 和 set,底层通常基于红黑树实现。详细讲解了 set 的去重排序特性及 multiset 用法,map 的键值对存储与 operator[] 功能,以及 multimap 的应用。通过经典例题如数组交集、链表环检测、单词频率统计等展示了实际应用场景。同时对比了有序容器与无序容器的底层结构与性能差异,总结了核心重点与常见易错点,帮助开发者高效掌握关联式容器的使用技巧。

氛围发布于 2026/3/15更新于 2026/10/2696 浏览
C++ STL 关联式容器详解:map 与 set 使用

📘 一、序列式容器 vs 关联式容器

1.1 什么是序列式容器?

我们之前学过的 string、vector、list、deque、array、forward_list 都属于序列式容器。
它们的特点是:元素按照插入的物理顺序存储,逻辑结构是线性的,元素之间没有特别紧密的关联关系。

就像一列火车,车厢(元素)按顺序排列,你可以通过位置(下标或迭代器)直接访问。


1.2 什么是关联式容器?

关联式容器(如 map、set、multimap、multiset)则是按关键字(key)来保存和访问数据的,逻辑结构通常是非线性的(如树或哈希表)。

  • set:只存储 key
  • map:存储 key-value 键值对
  • 它们底层通常使用红黑树(平衡二叉搜索树)实现,因此插入、删除、查找的时间复杂度都是 O(logN),并且遍历时元素是有序的(按 key 排序)。

就像字典,我们通过'单词'(key)查找'释义'(value),而不是通过第几页。


🧩 二、set 系列详解

2.1 set 的基本特性
  • 元素唯一(自动去重)
  • 默认按 key 升序排序(可通过仿函数改为降序)
  • 不允许修改 key(会破坏红黑树结构)
  • 底层为红黑树,支持双向迭代器
#include <set>
#include <iostream>
using namespace std;

int main() {
    set<int> s = {5, 2, 8, 2, 9, 1};
    for (int num : s) {
        cout << num << " "; // 输出:1 2 5 8 9(自动去重 + 排序)
    }
    return 0;
}

2.2 重要接口与使用示例
✅ 构造方式
set<int> s1; // 默认构造
set<int> s2(s1.begin(), s1.end()); // 迭代器区间构造
set<int> s3 = {1, 2, 3, 4}; // 初始化列表构造
set<int, greater<int>> s4; // 降序 set
✅ 插入与遍历
s.insert(10);
s.insert({20, 30, 40}); // 支持 initializer_list
// 遍历(只读)
for (auto it = s.begin(); it != s.end(); ++it) {
    // *it = 100; // ❌ 错误!不能修改 key
    cout << *it << " ";
}
✅ 查找与删除
auto pos = s.find(30);
if (pos != s.end()) {
    s.erase(pos); // 通过迭代器删除
}
size_t num = s.erase(100); // 直接删除值,返回删除个数(0 或 1)
// 使用 count 判断是否存在
if (s.count(20)) {
    cout << "20 exists" << endl;
}

2.3 multiset:允许重复元素的 set
multiset<int> ms = {5, 2, 5, 1, 5};
cout << ms.count(5); // 输出:3
ms.erase(5); // 删除所有值为 5 的元素

🔴 易错点:multiset::erase(value) 会删除所有等于该值的元素,若只想删除一个,需先 find 获取迭代器再删除。


2.4 经典例题解析
📌 349. 两个数组的交集
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> res;
    auto it1 = s1.begin(), it2 = s2.begin();
    while (it1 != s1.end() && it2 != s2.end()) {
        if (*it1 < *it2)
            ++it1;
        else if (*it1 > *it2)
            ++it2;
        else {
            res.push_back(*it1);
            ++it1;
            ++it2;
        }
    }
    return res;
}

✅ 优点:利用 set 自动去重 + 有序,双指针遍历即可,时间复杂度 O(NlogN)。


📌 142. 环形链表 II(set 降维打击版)
ListNode* detectCycle(ListNode* head) {
    set<ListNode*> visited;
    ListNode* cur = head;
    while (cur) {
        if (visited.count(cur)) return cur;
        visited.insert(cur);
        cur = cur->next;
    }
    return nullptr;
}

✅ 优点:代码简洁,逻辑清晰,无需复杂数学证明,时间复杂度 O(NlogN),空间复杂度 O(N)。


🗺️ 三、map 系列详解

3.1 map 的基本特性
  • 存储键值对 pair<const Key, T>
  • key 唯一,按 key 排序
  • 支持通过 key 快速查找 value
  • 允许修改 value,不允许修改 key

3.2 pair 类型简介
pair<string, int> p1 = {"Alice", 25};
cout << p1.first << " " << p1.second; // Alice 25
auto p2 = make_pair("Bob", 30); // 使用 make_pair 构造

3.3 重要接口与使用示例
✅ 构造与遍历
map<string, int> m = {{"Alice", 90}, {"Bob", 85}};
for (const auto& kv : m) {
    cout << kv.first << ": " << kv.second << endl;
}
// 或使用迭代器
for (auto it = m.begin(); it != m.end(); ++it) {
    cout << it->first << ": " << it->second << endl;
}
✅ 插入的四种方式
m.insert({"Charlie", 88});
m.insert(make_pair("David", 92));
m.insert(pair<string, int>("Eve", 95));
m["Frank"] = 70; // 最常用!若不存在则插入,存在则修改

🔴 易错点:insert 时如果 key 已存在,不会覆盖原有 value,而 operator[] 会。


✅ operator[] 的三重功能
map<string, int> m;
m["apple"] = 10; // 1. 插入(key 不存在)
m["apple"] = 20; // 2. 修改(key 存在)
int num = m["apple"]; // 3. 查找(返回 value,若不存在则插入默认值 0)

✅ 内部实现简析:


3.4 multimap:允许重复 key 的 map
  • 不支持 operator[](因为一个 key 可能对应多个 value)
  • find 返回第一个匹配的迭代器
  • count 返回 key 的出现次数
multimap<string, int> mm;
mm.insert({"A", 1});
mm.insert({"A", 2});
cout << mm.count("A"); // 输出:2

3.5 经典例题解析
📌 统计单词频率(map 的经典应用)
string arr[] = {"apple", "banana", "apple", "orange", "banana", "apple"};
map<string, int> countMap;
// 方法 1:find + insert
for (const auto& word : arr) {
    auto it = countMap.find(word);
    if (it == countMap.end()) {
        countMap.insert({word, 1});
    } else {
        it->second++;
    }
}
// 方法 2:operator[] 一行搞定
for (const auto& word : arr) {
    countMap[word]++;
}
for (const auto& kv : countMap) {
    cout << kv.first << ": " << kv.second << endl;
}

📌 138. 随机链表的复制(map 映射法)
Node* copyRandomList(Node* head) {
    map<Node*, Node*> nodeMap;
    Node* cur = head;
    // 第一遍:建立原节点 -> 新节点的映射
    while (cur) {
        nodeMap[cur] = new Node(cur->val);
        cur = cur->next;
    }
    // 第二遍:设置 next 和 random
    cur = head;
    while (cur) {
        nodeMap[cur]->next = nodeMap[cur->next];
        nodeMap[cur]->random = nodeMap[cur->random];
        cur = cur->next;
    }
    return nodeMap[head];
}

✅ 优点:逻辑清晰,无需修改原链表,时间复杂度 O(N)。


📌 692. 前 K 个高频单词(map + 排序)
vector<string> topKFrequent(vector<string>& words, int k) {
    map<string, int> countMap;
    for (const auto& w : words) countMap[w]++;
    vector<pair<string, int>> vec(countMap.begin(), countMap.end());
    // 自定义排序:频率降序,频率相同时字典序升序
    sort(vec.begin(), vec.end(), [](const auto& a, const auto& b) {
        return a.second > b.second || (a.second == b.second && a.first < b.first);
    });
    vector<string> res;
    for (int i = 0; i < k; ++i) res.push_back(vec[i].first);
    return res;
}

🔄 四、有序 vs 无序容器

特性set/map(有序)unordered_set/unordered_map(无序)
底层结构红黑树哈希表
时间复杂度O(logN)平均 O(1),最坏 O(N)
是否有序是(按 key 排序)否
是否需要哈希函数否是
迭代器稳定性稳定不稳定(重组时失效)

📌 选择建议:如果需要有序遍历,选 set/map;如果只关心存在性/快速查找,且不关心顺序,选 unordered_xxx;如果 key 是自定义类型,使用 unordered_xxx 需自定义哈希函数。


💎 五、核心总结与易错点

✅ 必须掌握的重点:
  1. set 用于去重排序,map 用于键值映射
  2. operator[] 在 map 中具有插入、修改、查找三重功能
  3. 关联式容器的迭代器是双向迭代器,支持 ++ 和 --
  4. 红黑树保证操作时间复杂度为 O(logN),且遍历有序
❌ 常见易错点:
  1. 试图修改 set 或 map 的 key → 编译错误
  2. 误以为 map::insert 会覆盖 value → 实际上不会
  3. 对 multimap 使用 operator[] → 编译错误(不支持)
  4. 忽略 unordered_xxx 的最坏时间复杂度 → 哈希冲突可能导致 O(N)
  5. 遍历时删除元素未更新迭代器 → 可能导致未定义行为

🚀 六、进阶练习建议

  1. 尝试用 map 实现一个简单的电话簿系统(姓名→电话)
  2. 用 set 实现论文查重中的停用词过滤
  3. 结合 priority_queue 和 map 实现实时 TopK 统计
  4. 自定义一个类作为 map 的 key,实现比较运算符或哈希函数

目录

  1. 📘 一、序列式容器 vs 关联式容器
  2. 1.1 什么是序列式容器?
  3. 1.2 什么是关联式容器?
  4. 🧩 二、set 系列详解
  5. 2.1 set 的基本特性
  6. 2.2 重要接口与使用示例
  7. ✅ 构造方式
  8. ✅ 插入与遍历
  9. ✅ 查找与删除
  10. 2.3 multiset:允许重复元素的 set
  11. 2.4 经典例题解析
  12. 📌 349. 两个数组的交集
  13. 📌 142. 环形链表 II(set 降维打击版)
  14. 🗺️ 三、map 系列详解
  15. 3.1 map 的基本特性
  16. 3.2 pair 类型简介
  17. 3.3 重要接口与使用示例
  18. ✅ 构造与遍历
  19. ✅ 插入的四种方式
  20. ✅ operator[] 的三重功能
  21. 3.4 multimap:允许重复 key 的 map
  22. 3.5 经典例题解析
  23. 📌 统计单词频率(map 的经典应用)
  24. 📌 138. 随机链表的复制(map 映射法)
  25. 📌 692. 前 K 个高频单词(map + 排序)
  26. 🔄 四、有序 vs 无序容器
  27. 💎 五、核心总结与易错点
  28. ✅ 必须掌握的重点:
  29. ❌ 常见易错点:
  30. 🚀 六、进阶练习建议

更多推荐文章

查看全部
  • AI 产品经理核心能力模型与转型路径
  • Linux 复习指南:Shell 脚本中最常见指令总结
  • 使用星辰 RPA 搭建小红书自动发文机器人
  • Strapi 快速部署实战:3 分钟为前端项目配置 API 后台
  • Tauri 架构解析:从 WebView 到工具链与生态
  • Python 数据分析与可视化全面指南
  • 基于 Java 与高德地图的县域烟花销售点 GIS 盘点实践
  • FunASR 离线文件转写服务开发指南与实践
  • 初学者如何选择编程语言和开发方向指南
  • iOS 新系统适配:TabBar 液态玻璃效果与 WiFi SSID 获取方案
  • Linux 服务器通用安全加固指南 - 基本系统安全
  • 基于 Pygame 的 Python 贪吃蛇游戏开发实战教程
  • Jetson Orin NX 16G 配置 AI 服务自动启动:Ollama、llama-server 与 OpenClaw Gateway
  • VMware 17 Ubuntu 虚拟机与宿主机复制粘贴失效修复
  • Java 集成 modbus4j 3.0.3 实现 PLC 数据采集与连接管理
  • 无线联邦学习:隐私保护下的 AI 协同训练
  • Linux 普通用户安装 Claude Code 全流程指南
  • Java 基础算法实战:字符转 ASCII 与四舍五入
  • 工业具身智能落地:从识别到执行的实操思路
  • 七款主流大模型英文降重能力实测与对比

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如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