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

C++ STL 容器详解:map 与 set 原理及实战

综述由AI生成C++ STL 中 map 和 set 基于红黑树实现有序存储。set 用于键值唯一场景,支持去重;map 用于键值对映射,key 不可变。本文详解构造、增删查改接口及 operator[] 底层逻辑,结合代码示例展示实际应用。重点讲解了 pair 的使用、multiset/multimap 的区别以及在实际算法题中的高效用法。

古灵精怪发布于 2026/3/21更新于 2026/6/920 浏览
C++ STL 容器详解:map 与 set 原理及实战

C++ STL 容器详解:map 与 set 原理及实战

在 C++ 的 STL 体系中,除了常见的序列式容器(如 vector、list),关联式容器同样占据核心地位。它们主要用于处理键值映射或唯一性集合的场景。本文将深入探讨基于红黑树实现的 map 和 set,结合代码示例讲解其底层逻辑与实战用法。

一、序列式与关联式容器

序列式容器(如 string、vector、list)按存储位置线性保存数据,元素间无紧密关联。而关联式容器则不同,它们通常采用非线性结构(如树或哈希表),通过关键字来保存和访问元素。

STL 中的关联式容器主要分为两类:

  1. 树形结构:包括 map、set、multimap、multiset。底层均为平衡二叉搜索树(红黑树),支持有序遍历。
  2. 哈希结构:包括 unordered_map、unordered_set。底层为哈希表,查找效率高但不保证顺序。

简单记忆:set 是 key 搜索场景,map 是 key/value 搜索场景。

二、键值对基础

关联式容器的核心在于'键值对'(Key-Value Pair)。这种结构包含两个成员变量:key 代表键值,value 表示对应的信息。例如字典中,英文单词是 key,中文含义是 value。

在 C++ 标准库中,pair 模板类用于将两个不同类型的数据组合在一起,它是 map 等容器内部存储的基本单元。

三、Set 容器详解

1. Set 简介

std::set 是按照一定次序存储元素的容器。其特点如下:

  • 唯一性:每个 value 必须是唯一的,插入重复元素会失败。因此常用于去重。
  • 有序性:默认按小于号(<)排序,内部使用红黑树实现。
  • 不可修改 Key:为了保证树的结构不被破坏,set 中的元素一旦插入便不能修改 key 值。
  • 查找效率:时间复杂度为 O(log n)。

虽然 set 只存放 value,但在底层实际存放的是由 <value, value> 构成的键值对。

2. 构造与迭代器

set 支持正向和反向迭代遍历,默认按升序顺序(中序遍历)。需要注意的是,set 的 iterator 不支持修改数据,因为修改关键字会破坏底层搜索树结构。

#include <set>
#include <iostream>
using namespace std;

int main() {
    // 默认升序
    set<int> s1;
    // 降序,传入 greater 仿函数
    set<int, greater<int>> s2;
    
    s2.insert();
    s();
    s();
    s();

    
     ( it = s(); it != s(); ++it) {
        cout << *it << ;
    }
    cout << endl;

    
     ( & e : s2) {
        cout << e << ;
    }
     ;
}
1
2.
insert
4
2.
insert
-2
2.
insert
100
// 使用迭代器遍历
for
auto
2.
begin
2.
end
" "
// 范围 for 循环更简洁
for
const
auto
" "
return
0

3. 增删查操作

set 的核心接口包括 insert、find、erase、count 以及区间查询 lower_bound/upper_bound。

  • insert: 返回 pair<iterator, bool>,bool 指示是否插入成功。
  • find: 返回迭代器,若未找到返回 end()。
  • erase: 可删除指定位置、指定值或区间。
  • count: 对于 set,返回 0 或 1;对于 multiset,返回实际个数。
增删查示例
#include <set>
#include <iostream>
using namespace std;

int main() {
    set<int> s = { 1, 4, 0, 34, 12, 56 };
    
    // 删除最小值
    s.erase(s.begin());
    
    // 直接删除值,返回删除的元素个数
    int x = 0;
    cin >> x;
    if (s.erase(x) == 0) {
        cout << x << "不存在!" << endl;
    }
    
    // 先查找再删除
    int y = 0;
    cin >> y;
    auto pos = s.find(y);
    if (pos != s.end()) {
        s.erase(pos);
    }
    
    // 利用 count 判断存在性
    if (s.count(x)) {
        cout << x << "在!" << endl;
    }
    return 0;
}
区间查询 lower_bound / upper_bound

这两个接口非常实用,用于查找大于等于或大于某值的第一个元素,常用于处理区间问题。

set<int> s = { 10, 20, 35, 40, 50, 65, 70, 80, 90 };
// 查找 >= 30 的位置
auto itlow = s.lower_bound(30);
// 查找 > 60 的位置
auto itup = s.upper_bound(60);
// 删除 [30, 60] 区间的值
s.erase(itlow, itup);

4. Multiset 与 Set 的差异

multiset 允许元素冗余(即可以存储多个相同的值)。主要差异体现在:

  • insert: 总是成功插入。
  • find: 返回中序第一个匹配项。
  • count: 返回该值出现的总次数。
  • erase(val): 删除所有等于 val 的元素。
multiset<int> s = { 1, 4, 3, 4, 6, 4, 8, 3, 0 };
for (auto e : s) {
    cout << e << " "; // 输出:0 1 3 3 4 4 4 6 8
}

5. 实战应用:LeetCode 题目

两个数组的交集

利用 set 的去重特性,可以快速找出两个数组的公共元素。

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> ret;
        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; // 入环节点
            s.insert(cur);
            cur = cur->next;
        }
        return nullptr;
    }
};

四、Map 容器详解

1. Map 简介

std::map 按照特定的次序(key 比较)存储键值对。其特点:

  • Key 唯一:key 用于排序和唯一标识元素。
  • Value 可变:value 存储与 key 关联的内容,可以通过迭代器修改。
  • 底层结构:红黑树,支持有序迭代。
  • Pair 绑定:内部将 key 和 value 绑定为 pair<const Key, T>。

2. Pair 与 Make_pair

在 map 中,我们频繁使用 pair 来存储键值对。

  • pair: 需要显式指定类型,如 pair<string, string>("a", "b")。
  • make_pair: 函数模板,自动推导类型,如 make_pair("a", "b")。

推荐使用 make_pair 或初始化列表,减少类型书写负担。

3. 插入方式

map 支持多种插入场景,灵活度很高:

map<string, string> dict;
pair<string, string> kv1("sort", "排序");
dict.insert(kv1); // 使用对象
dict.insert({ "left", "左边" }); // 隐式转换
dict.insert(make_pair("right", "右边")); // make_pair

// 初始化列表构造
map<string,string> dic2 = { 
    {"string", "字符串"}, 
    {"left", "左边"} 
};

注意:dict.insert({"k", "v"}) 实际上是调用 pair 构造函数,而非 initializer_list 构造 map。

4. 迭代与修改

map 的迭代器指向 pair<const Key, T>。Key 部分不可修改,Value 部分可以修改。

auto it = dict.begin();
while (it != dict.end()) {
    // it->first += 'x'; // 错误:Key 不可改
    it->second += "y";   // 正确:Value 可改
    ++it;
}

5. operator[] 的底层逻辑

这是 map 最常用但也最容易误解的接口。operator[] 不仅支持查找,还支持插入和修改。

原理:

  1. 如果 key 不存在,operator[] 会先插入 {key, mapped_type()}(默认构造的值),然后返回 value 的引用。
  2. 如果 key 存在,直接返回 value 的引用。

这意味着 map[key]++ 这种写法是合法的:它会自动创建新条目并计数,或者更新旧条目。

// 统计水果出现次数
string arr[] = { "苹果", "西瓜", "苹果", "香蕉" };
map<string, int> countMap;

// 优化写法:利用 [] 的自动插入 + 引用特性
for (const auto& str : arr) {
    countMap[str]++;
}

// 对比传统写法(需先 find)
for (const auto& str : arr) {
    auto ret = countMap.find(str);
    if (ret == countMap.end()) {
        countMap.insert({str, 1});
    } else {
        ret->second++;
    }
}

6. Erase 注意事项

erase 可能会导致迭代器失效。在遍历过程中删除元素时,务必使用 erase 的返回值更新迭代器,或使用 C++11 后的 it = container.erase(it) 模式。

五、Multimap

multimap 类似于 map,但允许 key 重复。由于 key 可能不唯一,operator[] 无法确定返回哪个 value,因此 multimap 重载了 operator[] 的行为或不提供该功能(具体视编译器实现而定,标准建议避免使用)。查找时使用 equal_range 获取一个区间。

六、总结

  • Set/Multiset: 用于唯一性或可重复的键值集合,底层红黑树,有序。
  • Map/Multimap: 用于键值映射,key 唯一或可重复,底层红黑树,有序。
  • Operator[]: 仅适用于 map,兼具查找、插入、修改功能,但需注意性能开销(涉及插入时的树调整)。
  • Pair: 理解 map 内部存储结构的关键,便于手动构建键值对。

掌握这些容器的特性与底层机制,能帮助我们在算法设计和工程开发中做出更合适的选择。

目录

  1. C++ STL 容器详解:map 与 set 原理及实战
  2. 一、序列式与关联式容器
  3. 二、键值对基础
  4. 三、Set 容器详解
  5. 1. Set 简介
  6. 2. 构造与迭代器
  7. 3. 增删查操作
  8. 增删查示例
  9. 区间查询 lowerbound / upperbound
  10. 4. Multiset 与 Set 的差异
  11. 5. 实战应用:LeetCode 题目
  12. 两个数组的交集
  13. 环形链表检测
  14. 四、Map 容器详解
  15. 1. Map 简介
  16. 2. Pair 与 Make_pair
  17. 3. 插入方式
  18. 4. 迭代与修改
  19. 5. operator[] 的底层逻辑
  20. 6. Erase 注意事项
  21. 五、Multimap
  22. 六、总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • FLUX.1 AI 绘画镜像免配置部署:内置中文界面与双语 Web UI
  • SkyWalking 多语言探针现状:.NET、C++ 与 Lua 实践指南
  • SkyWalking - .NET / C++ / Lua 探针现状与社区支持
  • 基于 SpringBoot 的安全生产举报信息统计系统设计与实现
  • SkyWalking .NET / C++ / Lua 探针现状与社区支持
  • RJ TextEd v16.54 免费多功能 Web 开发编辑器
  • Android CameraX 与 Camera2 框架机制及性能差异分析
  • Java LLM 开发框架全面解析:从 Spring AI 到 Agents-Flex
  • 基于 Dify 低代码平台开发 AI 应用:从入门到生产部署
  • 基于 Z-Image-Turbo 的本地 AI 绘画部署实战
  • 机器人阻抗控制器与导纳控制器原理对比
  • Python 七大主流就业方向及核心技能解析
  • Python-Binance 库:Binance 平台 API 集成开发指南
  • Python + AI:构建智能害虫识别系统实战
  • GESP-C++四级考试核心知识点与编程模板
  • OpenClaw 腾讯云服务器部署与飞书对接实战
  • Oracle 11g 自动收集统计信息导致 IO 过高优化方案
  • C++ 视觉开发:ONNX Runtime(ORT)使用指南
  • 低代码/无代码平台的幻象与现实:软件定制开发中的深层悖论
  • 自然语言处理在医疗领域的应用与实战

相关免费在线工具

  • 加密/解密文本

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