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

【高阶数据结构】map,set,multiset,multimap的使用和介绍

目录

      • 一、核心思想:为什么需要 `map` 和 `set`?
      • 二、底层基石:共同的实现基础
      • 三、`set` 详解:纯粹的集合
        • 1. 核心概念
        • 2. 关键接口与深度解析
      • 四、`map` 详解:键值对字典
        • 1. 核心概念
        • 2. 关键接口与深度解析
      • 五、`map` 和 `set` 的对比总结
      • 六、进阶话题:`multiset` 和 `multimap`
      • 七、核心概念:关联式容器
      • 八、总结与最佳实践

好的,我将基于我的理解,为你系统性地详细讲解 C++ STL 中的 mapset。我会从设计哲学到底层实现,再到具体使用,进行由浅入深的剖析。


一、核心思想:为什么需要 mapset

在编程中,我们经常遇到两种基本需求:

  1. 快速判断一个元素“存在与否”。比如,检查一个用户名是否已被注册。(set)
  2. 建立一种“对应关系”。比如,通过一个学生的学号,快速找到他的成绩。(map)

vector, list 等序列式容器在处理这些问题时,效率是低下的(需要遍历,O(N)复杂度)。mapset 就是为了高效解决这类“查找”问题而生的,它们的核心优势在于 O(log N) 的查找效率


二、底层基石:共同的实现基础

mapset(以及它们的多键版本 multimap, multiset) 在 STL 中通常使用同一种底层数据结构实现:红黑树

  1. 什么是红黑树?
    它是一种自平衡的二叉搜索树。普通的二叉搜索树在插入有序数据时会退化成链表,查找效率降至 O(N)。红黑树通过一套复杂的规则(节点颜色、旋转)在插入和删除时自动调整,保证了树的高度始终大致平衡。

  2. 为什么是红黑树?

    • 效率保证:得益于平衡性,对红黑树的增、删、查、改操作的时间复杂度都稳定在 O(log N)。这是一个在数据量巨大时依然非常优秀的性能。
    • 有序性:对二叉搜索树进行中序遍历,会得到一个有序的序列。这正是 mapset 迭代器遍历结果有序的原因。

结论:你可以把 mapset 想象成一个封装好的、功能强大的、自动排序且查找飞快的“智能树”。


三、set 详解:纯粹的集合

1. 核心概念

set 是一个存储 唯一元素 的容器。你可以把它理解为一个数学上的集合,或者一个自动去重且排序的数组

  • Key 即 Value:在 set 中,你存入的值 (value) 本身就是它的键 (key)。
  • 唯一性:集合中不允许存在两个相同的元素。
  • 不可变性:一旦元素被插入,你不能修改它的值。因为修改值可能会破坏红黑树的结构。要想修改,只能先删除旧值,再插入新值。
2. 关键接口与深度解析

a. 插入:insert
函数原型:

1. 单个元素插入
pair<iterator,bool> insert(const value_type& val);    //value_type:就是set元素
2. 范围插入
template <class InputIterator>void insert(InputIterator first, InputIterator last);
#include <set>
std::set<int> mySet;// 方式1:插入单个值,返回一个pair
std::pair<std::set<int>::iterator, bool> ret = mySet.insert(5);
// ret.first 是指向新插入元素(或已存在元素)的迭代器
// ret.second 是一个bool,表示插入是否成功 (true表示成功,false表示元素已存在)if (ret.second) {std::cout << "插入 5 成功" << std::endl;
} else {std::cout << "5 已存在" << std::endl;
}// 方式2:范围插入,使用初始化列表 (C++11)
mySet.insert({1, 3, 7, 2}); // mySet 现在是 {1, 2, 3, 5, 7}

b. 查找:find

auto it = mySet.find(3); // 查找值为3的元素// !!!最重要的检查!!!
if (it != mySet.end()) {// 找到了,it 是指向 3 的迭代器std::cout << "找到了: " << *it << std::endl;
} else {// 没找到,it 等于 mySet.end()std::cout << "未找到" << std::endl;
}

为什么 find 快? 因为它使用的是红黑树的二分查找,是 O(log N),而不是像在 vector 里线性扫描的 O(N)。

c. 遍历:有序的秘密

// 迭代器遍历
for (std::set<int>::iterator it = mySet.begin(); it != mySet.end(); ++it) {std::cout << *it << " "; // 输出:1 2 3 5 7 (升序)
}// 范围for循环 (更简洁)
for (const auto& num : mySet) {std::cout << num << " ";
}

遍历的顺序就是红黑树的中序遍历结果,所以是有序的。

d. 删除:erase

函数原型:

(1)	void erase (iterator position);
(2)	size_type erase (const value_type& val);  
(3)	void erase (iterator first, iterator last);

举例:

// 按值删除
size_t num_removed = mySet.erase(2); // 返回删除的元素个数,对于set是0或1
std::cout << "删除了 " << num_removed << " 个元素" << std::endl;// 按迭代器删除 
auto it = mySet.find(5);
if (it != mySet.end()) {mySet.erase(it);
}// 删除一个区间 [first, last)
mySet.erase(mySet.begin(), std::next(mySet.begin(), 2)); // 删除前两个元素

四、map 详解:键值对字典

1. 核心概念

map 是一个存储 键值对 (key-value pair) 的容器。它像一个字典,通过一个 key(如单词)去查找对应的 value(如释义)。

  • 键值对:每个元素都是一个 std::pair<const Key, T>
    • firstkey,被声明为 const不可修改
    • secondvalue可以修改
  • Key 的唯一性key 是唯一的。
2. 关键接口与深度解析

a. 插入:insert
函数原型:

1. 单个元素插入
pair<iterator,bool> insert(const value_type& val);   //value_type:就是map元素,即pair
2. 范围插入
template <class InputIterator>void insert(InputIterator first, InputIterator last);

举例:

#include <map>
#include <string>
std::map<std::string, int> studentScores;//单个插入
// 方式1:插入pair
studentScores.insert(std::pair<const std::string, int>("Alice", 95));
// 方式2:使用make_pair (推荐,自动推导类型)
studentScores.insert(std::make_pair("Bob", 88));
// 方式3:使用花括号 (C++11)
studentScores.insert({"Charlie", 72});// insert的返回值同样是一个pair<iterator, bool>
auto ret = studentScores.insert({"Alice", 100});
if (!ret.second) {std::cout << "Key 'Alice' 已存在,插入失败。其值为: " << ret.first->second << std::endl;
}//2. 范围插入
map1.insert({ {1, "one"}, {2, "two"} });
map2.insert(map1.begin(), map1.end());

b. 访问与修改:operator[] - 最强大的功能
这是 map 区别于 set 的最重要接口。

// !!!神奇的特性 !!!
studentScores["David"] = 85; // 1. 如果"David"不存在,会自动插入并赋值为85
studentScores["Bob"] = 92;   // 2. 如果"Bob"已存在,会将其value修改为92int score = studentScores["Alice"]; // 3. 可以直接用来访问
std::cout << score << std::endl; // 输出 95

operator[] 的工作原理:

  1. 在 map 中查找指定的 key"David")。
  2. 如果找到,返回其 value 的引用。
  3. 如果没找到,则自动插入一个键值对 (key, T()),其中 T()value 类型的默认值(int() 是 0,string() 是空字符串),然后返回这个新 value 的引用。
    注意:正因有此“有则修改,无则插入”的特性,在只希望查询而不希望插入时,应使用 find,而不是 operator[]

c. 遍历:访问 keyvalue

// 迭代器访问,it->first 是key,it->second 是value
for (auto it = studentScores.begin(); it != studentScores.end(); ++it) {std::cout << it->first << " 的分数是: " << it->second << std::endl;
}// 范围for循环
for (const auto& kv : studentScores) { // kv 是一个pair的引用std::cout << kv.first << " : " << kv.second << std::endl;
}

d. 查找:find

// 根据key查找
auto it = studentScores.find("Bob");
if (it != studentScores.end()) {it->second = 100; // 可以通过迭代器修改valuestd::cout << "找到了,Bob的分数是:" << it->second << std::endl;
}

e. 删除:erase

函数原型:

(1)	void erase (iterator position);
(2)	size_type erase (const key_type& k);   //pair中的key
(3)	void erase (iterator first, iterator last);

举例:

// insert some values:
mymap['a'] = 10;
mymap['b'] = 20;
mymap['c'] = 30;
mymap['d'] = 40;
mymap['e'] = 50;
mymap['f'] = 60;auto it = mymap.find('b');
mymap.erase(it); // erasing by iteratormymap.erase('c'); // erasing by keyit = mymap.find('e');
mymap.erase(it, mymap.end()); // erasing by range

五、mapset 的对比总结

特性setmap
存储内容唯一的 key唯一的 key 及其对应的 value
元素类型Keypair<const Key, T>
核心用途检查存在性、去重建立映射关系、字典
修改元素不可修改 key不可修改 key可以修改 value
关键接口insert, find, eraseinsert, find, erase, operator[]
底层实现红黑树红黑树
时间复杂度增、删、查:O(log N)增、删、查:O(log N)
遍历结果key 有序key 有序

六、进阶话题:multisetmultimap

  • multiset / multimap:允许 重复的 key
  • 接口差异
    • insert 总是成功,返回迭代器,不返回 pair<...,bool>
    • erase(key) 会删除所有匹配的 key,并返回删除的个数。
    • find 返回第一个匹配元素的迭代器。
    • count(key) 变得有用,返回 key 的出现次数。
    • equal_range(key) 返回一个 pair<iterator, iterator>,表示所有等于 key 的元素范围。这是处理多重键最常用的接口。
  • multimap 没有 operator[],因为同一个 key 可能对应多个 value,无法确定返回哪一个。

好的,这是一份关于 C++ STL 中 mapset 的系统和仔细的讲解。本文将结合你提供的文章内容,进行更结构化、更深度的剖析。

七、核心概念:关联式容器

  1. 是什么mapset 是 C++ 标准模板库中的关联式容器
  2. 与序列式容器的区别
    • 序列式容器(vector, list, deque, string):存储的是元素本身,元素在容器中的位置取决于插入的时机和地点,与元素的值无关。底层通常是线性数据结构。
    • 关联式容器(map, set, multiset, multimap):存储的是 键值对 (key-value pair)键 (key) ,元素在容器中的位置取决于 键 (key) 的值,与插入顺序无关。底层通常是 平衡二叉搜索树(红黑树)
  3. 底层实现:它们通常都基于红黑树实现。这是一种自平衡的二叉搜索树,它保证了在最坏情况下,搜索、插入、删除操作的时间复杂度都是 O(log n)。这正是关联式容器效率高的原因。
  4. 核心特性:由于底层是二叉搜索树,对其进行中序遍历会得到一个关于 key 的有序序列

八、总结与最佳实践

  1. 何时使用?

    • 需要快速查找、去重、自动排序 -> 考虑 set
    • 需要建立 key-value 映射关系 -> 考虑 map
    • 允许重复键 -> 考虑 multiset / multimap
    • C++11 后,如果不需要排序,但需要极致查找性能 (O(1)),可以考虑 unordered_setunordered_map(基于哈希表)。
  2. 关键要点

    • 始终检查 find 的返回值是否等于 end()
    • 理解 map::operator[] 的“无则插入”特性,谨慎使用。
    • 迭代器遍历得到的是有序序列。
    • 它们的性能优势在大数据量时尤为明显。
http://www.dtcms.com/a/573563.html

相关文章:

  • Spring Boot + Spring Security ACL实现对特定领域对象的细粒度权限控制
  • 大模型应用03 || 函数调用 Function Calling || 概念、思想、流程
  • 从 Spring Boot 到 NestJS:模块化设计的哲学差异
  • WebSocket 使用
  • 郑州网络营销网站app上架应用市场需要什么条件
  • 百度网站官方认证怎么做郑州网站建设贴吧
  • Spring定时任务cron表达式解析
  • 做网站通过什么赚钱wordpress 主题 下载
  • MATLAB视觉检测系统详细介绍
  • 网络工程基础
  • 【NXP i.MX91】 RT-Linux移植
  • 怕随身 WiFi 虚量断连?格行随身wifi拆箱测评:1500G 真不虚标?
  • 门户网站建设摘要强大的wordpress瀑布流主题
  • Kubernetes1.23版本搭建(三台机器)
  • 远程桌面工具汇总:RustDesk、1Remote、CrossDesk
  • linux下动静态库
  • iss服务器网站建设防止网站流量被刷
  • 【机器学习16】连续状态空间、深度Q网络DQN、经验回放、探索与利用
  • 网络传输协议的介绍,HTTP、SSE、WebSocket
  • 上海做网站公司有哪些北京网站建设公司哪家实惠
  • iOS 基于 Foundation Model 构建媒体流
  • Zabbix 6.0 基于 LNMP 架构完整部署教程(CentOS7)
  • 接口自动化测试----高并发抽奖系统
  • 用Python来学微积分31-定积分的概念与几何意义详解
  • 使用 Python 语言 从 0 到 1 搭建完整 Web UI自动化测试学习系列 23--数据驱动--参数化处理 Yaml 文件
  • 基于SpringBoot的公务员考试管理系统【题库组卷+考试练习】
  • Nginx 反向代理 HTTPS CDN 配置检查清单(避坑版)
  • 网站套餐到期是什么意思减压轻松网站开发
  • 常见的矩阵运算方法与应用
  • SQLite 3.51.0发布,新功能解读