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

C++进阶:(五)map系列容器的全面解析

目录

前言

一、map 系列底层基石:红黑树与键值对存储

1.1 红黑树:保证高效增删查改的核心

1.2 键值对(pair):map 的核心数据单元

1.2.1 pair 结构体的本质

1.2.2 map 与 pair 的关联

二、map 容器核心特性与模板参数

2.1 map 的模板参数解析

2.2 map 的核心特性

三、map 容器核心接口详解与实战示例

3.1 构造函数与初始化

3.1.1 构造接口

3.1.2 示例:多种初始化方式

3.2 迭代器与遍历

3.2.1 迭代器接口

3.2.2 迭代器访问规则

3.2.3 示例:多种遍历方式

3.3 插入操作(insert)

3.3.1 插入接口

3.3.2 关键接口解析(单个键值对插入)

3.3.3 插入键值对的 4 种方式

3.3.4 示例:插入操作

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

3.4.1 查找接口

3.4.2 接口使用场景

3.4.3 示例:查找操作

3.5 删除操作(erase)

3.5.1 删除接口

3.5.2 关键注意事项

3.5.3 示例:删除操作

3.6 数据修改:迭代器与 operator [] 的核心用法

3.6.1 迭代器修改 value

3.6.2 operator []:多功能复合接口

1. operator [] 的声明与实现原理

2. operator [] 的三重功能

3. 实战示例:operator [] 的用法

4. operator [] 的注意事项

四、multimap 容器:支持 key 冗余的键值对映射

4.1 multimap 的核心特性

4.2 multimap 与 map 的接口差异

4.3 示例:multimap 的使用

4.4 multimap 的适用场景

五、map 系列容器的OJ题实战

5.1 场景一:随机链表的复制(键值对映射解决指针关联)

解题思路(基于 map)

代码实现

复杂度分析

5.2 场景二:前 K 个高频单词(map 统计 + 排序)

代码实现

复杂度分析

总结


前言

        在 C++ STL 容器体系中,map 系列是关联式容器的核心成员之一,专为键值对(key-value) 存储与高效查找场景设计。与 set 系列专注于 “单一关键字” 不同,map 系列通过 “关键字 - 值” 的映射关系,实现了数据的结构化存储与快速访问,广泛应用于字典查询、数据统计、缓存映射等场景。下面就让我们正式开始吧!


一、map 系列底层基石:红黑树与键值对存储

        首先给大家提供一下map系列的参考文档:https://legacy.cplusplus.com/reference/map/

        与set系列容器类似,map 系列(map、multimap)的高效性源于底层的红黑树(平衡二叉搜索树) 实现,而其核心功能则依赖于 “键值对” 的结构化存储。理解这两个基础要素,是掌握 map 系列的关键。下面我们就先来复习一下红黑树的相关知识:

1.1 红黑树:保证高效增删查改的核心

        map 系列与 set 系列共享相同的底层数据结构 —— 红黑树。红黑树通过以下 5 条规则确保自身始终处于 “近似平衡” 状态,从而使增删查改操作的时间复杂度稳定在O(log N)

  1. 每个节点要么是红色,要么是黑色;
  2. 根节点必须是黑色;
  3. 所有叶子节点(NIL 节点,空节点)均为黑色;
  4. 若一个节点是红色,则其两个子节点必须是黑色(避免连续红节点);
  5. 从任意节点到其所有后代叶子节点的路径上,黑色节点的数量相同(确保路径长度差异不超过 2 倍)。

        这种平衡特性使得 map 系列在处理大规模数据时,依然能保持高效的操作性能,避免了普通二叉搜索树在极端情况下(如有序插入)退化为链表、导致 O (N) 时间复杂度的问题。

1.2 键值对(pair):map 的核心数据单元

        map 系列存储的数据并非单一值,而是 “关键字(key)- 值(value)” 的映射对,底层通过pair<const Key, T>结构体实现这一映射关系。pair结构体的定义与特性如下所示:

1.2.1 pair 结构体的本质

    pair是 STL 中用于存储两个关联数据的模板结构体,其简化版的源码定义如下:

template <class T1, class T2>
struct pair {typedef T1 first_type;  // 第一个成员的类型(对应map的key)typedef T2 second_type; // 第二个成员的类型(对应map的value)T1 first;  // 关键字(key),在map中为const类型,不可修改T2 second; // 值(value),可修改// 无参构造:默认初始化两个成员pair() : first(T1()), second(T2()) {}// 带参构造:通过指定值初始化pair(const T1& a, const T2& b) : first(a), second(b) {}// 拷贝构造:从其他pair对象拷贝初始化template<class U, class V>pair(const pair<U, V>& pr) : first(pr.first), second(pr.second) {}
};

1.2.2 map 与 pair 的关联

        在 map 中,pairfirst成员被限定为const 类型const Key),这是因为 key 是红黑树排序的依据 —— 修改 key 会破坏红黑树的有序结构,导致容器功能异常;而second成员(value)则可以自由修改,满足 “通过 key 查找并更新 value” 的需求。

        为了方便创建pair对象,STL 提供了make_pair函数,无需显式指定模板参数即可生成pair实例:

template <class T1, class T2>
inline pair<T1, T2> make_pair(T1 x, T2 y) {return pair<T1, T2>(x, y);
}

        例如,创建一个 “字符串 - 整数” 的键值对,可通过以下两种方式:

// 1. 直接构造pair(需显式指定模板参数)
pair<string, int> kv1("apple", 5);// 2. 通过make_pair构造(自动推导类型,更简洁)
auto kv2 = make_pair("banana", 3);

二、map 容器核心特性与模板参数

        map 是 map 系列中最常用的容器,其关键是 “有序、唯一键值对映射”—— 即 key 唯一且按指定规则排序,value 可通过 key 快速查找与修改。

2.1 map 的模板参数解析

        map 的模板定义决定了其 key 类型、value 类型、排序规则与内存分配方式,其完整声明如下:

template < class Key,                     // 1. 关键字(key)的类型class T,                       // 2. 值(value)的类型class Compare = less<Key>,     // 3. 排序规则(默认升序)class Alloc = allocator<pair<const Key, T>>  // 4. 空间配置器
> class map;

        各模板参数的作用:

  1. Key:即键的类型,需支持Compare指定的比较规则(默认支持<运算符)。例如map<int, string>中,Key 为int类型。

  2. T:即值的类型,可任意指定(如intstring、自定义结构体等)。在 map 中,T 被 typedef 为mapped_type,而value_type则是pair<const Key, T>(红黑树节点存储的实际类型)。

  3. Compare:用于定义 key 的排序规则,默认使用less<Key>(按 key 升序排列)。若需自定义排序(如降序、按字符串长度排序),可以传入自定义仿函数或 STL 内置仿函数。

  4. Alloc:空间配置器,负责内存的分配与释放,默认使用 STL 提供的allocator。一般情况下无需手动指定,仅在特殊内存管理场景(如内存池)中需要自定义。

        示例:创建一个按 key 降序排列的 map:

#include <iostream>
#include <map>
using namespace std;int main() {// 自定义排序规则:按key降序(使用STL内置的greater<int>)map<int, string, greater<int>> descMap = {{3, "apple"}, {1, "banana"}, {2, "orange"}};// 遍历:按key降序输出for (const auto& kv : descMap) {cout << kv.first << ": " << kv.second << endl;}/* 输出结果:3: apple2: orange1: banana*/return 0;
}

2.2 map 的核心特性

        基于底层红黑树与键值对存储,map 具备以下核心特性:

  1. key 唯一:map 中不允许存在重复的 key,插入已存在的 key 会失败;
  2. key 有序:元素按Compare指定的规则排序,遍历顺序为红黑树的中序遍历结果;
  3. 高效操作:增删查改的时间复杂度均为 O (log N),源于红黑树的平衡特性;
  4. 迭代器特性
    • 支持双向迭代器(iteratorreverse_iterator),可正向 / 反向遍历;
    • iterator指向的pair中,first(key)为 const 类型,不可修改;second(value)可修改;
    • const_iterator则完全只读,既不能修改 key,也不能修改 value;
  5. 支持范围 for:C++11 及以上标准中,可通过范围 for 简化遍历操作。

三、map 容器核心接口详解与实战示例

        STL 容器的接口设计具有高度一致性,map 的接口与 set 系列有诸多相似之处,但也因 “键值对” 特性存在独特接口(如operator[])。本节详解 map 的各个核心接口(构造、迭代器、增删查改),并通过实战示例说明使用场景。

3.1 构造函数与初始化

        map 主要提供了 4 种构造方式,覆盖空构造、区间构造、拷贝构造与初始化列表构造,满足不同场景的初始化需求。

3.1.1 构造接口

// 1. 无参构造:创建空map,使用默认比较规则与空间配置器
explicit map(const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type());// 2. 迭代器区间构造:用[first, last)区间的键值对初始化
template <class InputIterator>
map(InputIterator first, InputIterator last,const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type());// 3. 拷贝构造:用另一个map对象初始化
map(const map& x);// 4. 初始化列表构造:用初始化列表中的键值对初始化(最常用)
map(initializer_list<value_type> il,const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type());

3.1.2 示例:多种初始化方式

#include <iostream>
#include <map>
#include <vector>
using namespace std;int main() {// 1. 无参构造map<int, string> m1;m1.insert(make_pair(1, "one"));m1.insert({2, "two"});  // C++11列表初始化简化cout << "m1(无参构造+插入):" << endl;for (const auto& kv : m1) {cout << kv.first << ": " << kv.second << " ";}cout << endl;  // 输出:1: one 2: two // 2. 初始化列表构造(最简洁,推荐)map<int, string> m2 = {{3, "three"}, {4, "four"}, {5, "five"}};cout << "m2(初始化列表构造):" << endl;for (const auto& kv : m2) {cout << kv.first << ": " << kv.second << " ";}cout << endl;  // 输出:3: three 4: four 5: five // 3. 迭代器区间构造(用m2的部分区间初始化m3)auto start = ++m2.begin();  // 指向4: fourauto end = m2.end();        // 指向尾后迭代器map<int, string> m3(start, end);cout << "m3(迭代器区间构造):" << endl;for (const auto& kv : m3) {cout << kv.first << ": " << kv.second << " ";}cout << endl;  // 输出:4: four 5: five // 4. 拷贝构造(用m2初始化m4)map<int, string> m4(m2);cout << "m4(拷贝构造):" << endl;for (const auto& kv : m4) {cout << kv.first << ": " << kv.second << " ";}cout << endl;  // 输出:3: three 4: four 5: five return 0;
}

3.2 迭代器与遍历

        map 支持双向迭代器,遍历方式包括 “迭代器遍历” 与 “范围 for 遍历”,遍历顺序由Compare规则决定(默认升序)。

3.2.1 迭代器接口

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

3.2.2 迭代器访问规则

  • 迭代器指向的是pair<const Key, T>对象,需通过->first访问 key->second访问 value
  • 非 const 迭代器可修改second(value),但不可修改first(key);
  • const 迭代器完全只读,不可修改任何成员。

3.2.3 示例:多种遍历方式

#include <iostream>
#include <map>
using namespace std;int main() {map<string, int> fruitCount = {{"apple", 5}, {"banana", 3}, {"orange", 4}};// 1. 正向非const迭代器遍历(可修改value)cout << "正向遍历(修改value前):" << endl;auto it = fruitCount.begin();while (it != fruitCount.end()) {// it->first = "pear";  // 错误:key为const,不可修改cout << it->first << ": " << it->second << " ";it->second += 1;  // 合法:修改value++it;}cout << endl;  // 输出:apple: 5 banana: 3 orange: 4 // 2. 正向const迭代器遍历(完全只读)cout << "正向const遍历(修改value后):" << endl;map<string, int>::const_iterator cit = fruitCount.cbegin();while (cit != fruitCount.cend()) {// cit->second += 1;  // 错误:const迭代器不可修改valuecout << cit->first << ": " << cit->second << " ";++cit;}cout << endl;  // 输出:apple: 6 banana: 4 orange: 5 // 3. 反向迭代器遍历(逆序输出)cout << "反向遍历:" << endl;map<string, int>::reverse_iterator rit = fruitCount.rbegin();while (rit != fruitCount.rend()) {cout << rit->first << ": " << rit->second << " ";++rit;}cout << endl;  // 输出:orange: 5 banana: 4 apple: 6 // 4. 范围for遍历(最简洁,推荐)cout << "范围for遍历:" << endl;for (const auto& kv : fruitCount) {cout << kv.first << ": " << kv.second << " ";}cout << endl;  // 输出:apple: 6 banana: 4 orange: 5 return 0;
}

3.3 插入操作(insert)

        插入是 map 的核心接口之一,负责向红黑树中添加键值对,同时维持 key 的唯一性与有序性。

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 关键接口解析(单个键值对插入)

        返回值pair<iterator, bool>是判断插入结果的核心,其含义如下:

  • first:迭代器,指向 “新插入的键值对”(插入成功)或 “已存在的相同 key 的键值对”(插入失败);
  • second:布尔值,true表示插入成功(key 不存在),false表示插入失败(key 已存在)。

3.3.3 插入键值对的 4 种方式

        在插入单个键值对时,可通过以下 4 种方式创建value_typepair<const Key, T>对象,其中第 3、4 种方式最简洁:

  1. 直接构造pair(显式指定模板参数);
  2. 通过make_pair函数(自动推导类型);
  3. C++11 列表初始化({key, value});
  4. 直接传入pair临时对象。

3.3.4 示例:插入操作

#include <iostream>
#include <map>
using namespace std;int main() {map<int, string> dict;// 1. 插入方式1:直接构造pair(显式模板参数)pair<int, string> kv1(1, "one");auto ret1 = dict.insert(kv1);cout << "插入kv1(1, one):" << (ret1.second ? "成功" : "失败") << ",当前key对应的value:" << ret1.first->second << endl;  // 成功,one// 2. 插入方式2:make_pair(自动推导类型)auto ret2 = dict.insert(make_pair(2, "two"));cout << "插入make_pair(2, two):" << (ret2.second ? "成功" : "失败") << ",当前key对应的value:" << ret2.first->second << endl;  // 成功,two// 3. 插入方式3:C++11列表初始化(最简洁)auto ret3 = dict.insert({3, "three"});cout << "插入{3, three}:" << (ret3.second ? "成功" : "失败") << ",当前key对应的value:" << ret3.first->second << endl;  // 成功,three// 4. 插入方式4:插入重复key(失败案例)auto ret4 = dict.insert({3, "three_new"});cout << "插入{3, three_new}:" << (ret4.second ? "成功" : "失败") << ",当前key对应的value:" << ret4.first->second << endl;  // 失败,three// 5. 批量插入:初始化列表dict.insert({4, "four"}, {5, "five"});  // 错误:需用大括号包裹所有键值对dict.insert({{4, "four"}, {5, "five"}});  // 正确:批量插入两个键值对cout << "批量插入后dict:" << endl;for (const auto& kv : dict) {cout << kv.first << ": " << kv.second << " ";}cout << endl;  // 输出:1: one 2: two 3: three 4: four 5: five return 0;
}

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

        查找是 map 的核心优势,通过 key 快速定位 value,支持精确查找、计数查找与范围查找。

3.4.1 查找接口

// 1. 精确查找:返回指向key的迭代器,未找到返回end()
iterator find(const key_type& k);
const_iterator find(const key_type& k) const;// 2. 计数查找:返回key的个数(map中仅0或1,multimap中为实际个数)
size_type count(const key_type& k) const;// 3. 范围查找:返回第一个>=k的迭代器
iterator lower_bound(const key_type& k);
const_iterator lower_bound(const key_type& k) const;// 4. 范围查找:返回第一个>k的迭代器
iterator upper_bound(const key_type& k);
const_iterator upper_bound(const key_type& k) const;

3.4.2 接口使用场景

  • find:最常用的精确查找,需判断返回的迭代器是否为end()
  • count:仅用于判断 key 是否存在(map 中返回 0 或 1),效率与find一致(O (log N));
  • lower_bound/upper_bound:配合使用可获取 [key1, key2] 区间的键值对,适用于范围查询场景。

3.4.3 示例:查找操作

#include <iostream>
#include <map>
using namespace std;int main() {map<int, string> dict = {{1, "one"}, {2, "two"}, {3, "three"}, {4, "four"}, {5, "five"}};// 1. find精确查找int targetKey = 3;auto findIt = dict.find(targetKey);if (findIt != dict.end()) {cout << "找到key=" << targetKey << ",value=" << findIt->second << endl;  // 找到,three} else {cout << "未找到key=" << targetKey << endl;}targetKey = 6;findIt = dict.find(targetKey);if (findIt != dict.end()) {cout << "找到key=" << targetKey << ",value=" << findIt->second << endl;} else {cout << "未找到key=" << targetKey << endl;  // 未找到}// 2. count计数查找targetKey = 4;size_t count = dict.count(targetKey);cout << "key=" << targetKey << "的个数:" << count << endl;  // 1targetKey = 7;count = dict.count(targetKey);cout << "key=" << targetKey << "的个数:" << count << endl;  // 0// 3. lower_bound/upper_bound范围查找(查找key在[2,4]区间的键值对)auto lowIt = dict.lower_bound(2);  // 第一个>=2的迭代器(key=2)auto upIt = dict.upper_bound(4);   // 第一个>4的迭代器(key=5)cout << "key在[2,4]区间的键值对:" << endl;for (auto it = lowIt; it != upIt; ++it) {cout << it->first << ": " << it->second << " ";}cout << endl;  // 输出:2: two 3: three 4: four return 0;
}

3.5 删除操作(erase)

        删除操作用于移除 map 中的键值对,支持按 “迭代器位置”、“key 值” 或 “迭代器区间” 删除,接口简洁且高效。

3.5.1 删除接口

// 1. 按迭代器位置删除:返回删除元素的下一个元素的迭代器
iterator erase(const_iterator position);// 2. 按key值删除:返回删除的元素个数(map中0或1)
size_type erase(const key_type& k);// 3. 按迭代器区间删除:返回删除区间的下一个元素的迭代器
iterator erase(const_iterator first, const_iterator last);

3.5.2 关键注意事项

  • 按迭代器删除时,需确保迭代器有效(不为end()),否则会导致未定义行为;
  • 按 key 删除时,若 key 不存在,返回 0 且无任何操作;若存在,返回 1 且删除对应键值对;
  • 按区间删除时,区间需满足[first, last)(左闭右开),且first需在last之前。

3.5.3 示例:删除操作

#include <iostream>
#include <map>
using namespace std;int main() {map<int, string> dict = {{1, "one"}, {2, "two"}, {3, "three"}, {4, "four"}, {5, "five"}};// 1. 按迭代器位置删除(删除key=3)auto posIt = dict.find(3);if (posIt != dict.end()) {dict.erase(posIt);cout << "删除key=3后dict:" << endl;for (const auto& kv : dict) {cout << kv.first << ": " << kv.second << " ";}cout << endl;  // 输出:1: one 2: two 4: four 5: five }// 2. 按key值删除(删除key=5)size_t delCount = dict.erase(5);cout << "删除key=5:" << (delCount ? "成功" : "失败") << endl;  // 成功cout << "删除key=5后dict:" << endl;for (const auto& kv : dict) {cout << kv.first << ": " << kv.second << " ";}cout << endl;  // 输出:1: one 2: two 4: four // 3. 按区间删除(删除key在[2,4]区间)auto lowIt = dict.lower_bound(2);  // key=2auto upIt = dict.upper_bound(4);   // key=5(已被删除,实际为end())dict.erase(lowIt, upIt);cout << "删除区间[2,4]后dict:" << endl;for (const auto& kv : dict) {cout << kv.first << ": " << kv.second << " ";}cout << endl;  // 输出:1: one return 0;
}

3.6 数据修改:迭代器与 operator [] 的核心用法

        map 的数据修改仅针对valuesecond),keyfirst)因是红黑树排序依据而不可修改。修改value主要通过两种方式:迭代器修改operator [] 修改,其中operator[]是 map 最具特色的接口,兼具 “插入、查找、修改” 三重功能。

3.6.1 迭代器修改 value

        通过find接口获取目标 key 的迭代器后,直接修改it->second即可,适用于 “先查找再修改” 的场景。

示例

#include <iostream>
#include <map>
using namespace std;int main() {map<string, int> fruitCount = {{"apple", 5}, {"banana", 3}};// 查找并修改apple的数量auto it = fruitCount.find("apple");if (it != fruitCount.end()) {it->second += 2;  // 将5改为7}cout << "修改后fruitCount:" << endl;for (const auto& kv : fruitCount) {cout << kv.first << ": " << kv.second << " ";}cout << endl;  // 输出:apple: 7 banana: 3 return 0;
}

3.6.2 operator []:多功能复合接口

    operator[]是 map 最常用的接口之一,其核心原理基于insert接口实现,具备 “插入、查找、修改” 三重功能。

1. operator [] 的声明与实现原理
// 声明:返回value的引用,可修改
mapped_type& operator[](const key_type& k);// 内部实现(简化版)
mapped_type& operator[](const key_type& k) {// 尝试插入键值对{k, mapped_type()}(mapped_type()为默认值,如int默认0)pair<iterator, bool> ret = insert({k, mapped_type()});// 返回value的引用(无论插入成功与否,ret.first均指向目标key的迭代器)return ret.first->second;
}
2. operator [] 的三重功能
  • 功能 1:插入键值对:若 key 不存在,插入{k, 默认值}并返回 value 的引用;
  • 功能 2:查找 value:若 key 存在,直接返回 value 的引用;
  • 功能 3:修改 value:通过返回的引用直接修改 value。
3. 实战示例:operator [] 的用法
#include <iostream>
#include <map>
#include <string>
using namespace std;int main() {map<string, string> dict;// 1. 功能1:插入键值对(key不存在)dict["insert"];  // 插入{"insert", ""}(string默认空串)dict["left"] = "左边";  // 插入{"left", "左边"}cout << "插入后dict:" << endl;cout << "insert: " << dict["insert"] << endl;  // 输出:insert: cout << "left: " << dict["left"] << endl;      // 输出:left: 左边// 2. 功能2:查找value(key存在)string value = dict["left"];cout << "查找left的value:" << value << endl;  // 输出:左边// 3. 功能3:修改value(key存在)dict["left"] = "左边、剩余";  // 将"左边"改为"左边、剩余"cout << "修改后left的value:" << dict["left"] << endl;  // 输出:左边、剩余// 4. 综合示例:统计单词出现次数string words[] = {"apple", "banana", "apple", "orange", "apple"};map<string, int> wordCount;for (const auto& word : words) {wordCount[word]++;  // 关键:不存在则插入(默认0)并+1;存在则直接+1}cout << "单词统计结果:" << endl;for (const auto& kv : wordCount) {cout << kv.first << ": " << kv.second << "次" << endl;}/* 输出:apple: 3次banana: 1次orange: 1次*/return 0;
}
4. operator [] 的注意事项
  • 若仅需 “查找” 而不希望 “插入不存在的 key”,应使用find接口(operator[]会自动插入不存在的 key,可能导致意外数据);
  • 对于自定义类型的mapped_type,需确保其有默认构造函数(mapped_type()),否则operator[]无法使用;
  • operator[]返回的是mapped_type&,可直接修改 value,若需只读访问,可使用at()接口(C++11 及以上)或find配合const_iterator

四、multimap 容器:支持 key 冗余的键值对映射

        multimap 是 map 的 “兄弟容器”,底层同样基于红黑树实现,核心差异仅在于支持 key 冗余(即允许存在多个相同的 key)。这一特性导致 multimap 在接口行为与使用场景上与 map 存在显著区别。

4.1 multimap 的核心特性

  1. key 可冗余:允许插入多个相同的 key,每个 key 对应独立的 value;
  2. key 有序:与 map 一致,按Compare规则排序,相同 key 的键值对相邻存储;
  3. 高效操作:增删查改的时间复杂度仍为 O (log N)
  4. 无 operator [] 接口:因 key 可冗余,operator[]无法确定返回哪个 key 的 value,故不支持;
  5. 迭代器特性:与 map 一致,first(key)为 const 类型,second(value)可修改。

4.2 multimap 与 map 的接口差异

        multimap 与 map 的大部分接口一致,但因 key 冗余特性,以下接口的行为存在差异:

接口map(key 唯一)multimap(key 冗余)
insert返回pair<iterator, bool>,标识插入成功与否返回iterator,指向新插入的键值对(插入必成功)
find返回唯一 key 的迭代器,未找到返回 end ()返回中序遍历的第一个匹配 key 的迭代器
count返回 0 或 1(判断 key 是否存在)返回匹配 key 的实际个数
erase(按 key)删除 1 个匹配 key 的键值对,返回 0 或 1删除所有匹配 key 的键值对,返回删除个数
operator[]支持(插入、查找、修改)不支持

4.3 示例:multimap 的使用

#include <iostream>
#include <map>
using namespace std;int main() {// 1. 初始化multimap(插入重复key)multimap<int, string> courseMap;courseMap.insert({101, "C++"});courseMap.insert({101, "Data Structure"});  // 插入相同key=101courseMap.insert({102, "Python"});courseMap.insert({101, "Algorithm"});        // 再次插入key=101cout << "multimap初始化结果:" << endl;for (const auto& kv : courseMap) {cout << "Course ID: " << kv.first << ", Name: " << kv.second << endl;}/* 输出(按key升序,相同key相邻):Course ID: 101, Name: C++Course ID: 101, Name: Data StructureCourse ID: 101, Name: AlgorithmCourse ID: 102, Name: Python*/// 2. find接口:返回第一个匹配key的迭代器auto findIt = courseMap.find(101);if (findIt != courseMap.end()) {cout << "\n第一个key=101的课程:" << findIt->second << endl;  // 输出:C++// 遍历所有key=101的课程cout << "所有key=101的课程:" << endl;while (findIt != courseMap.end() && findIt->first == 101) {cout << findIt->second << " ";++findIt;}cout << endl;  // 输出:C++ Data Structure Algorithm}// 3. count接口:统计key的个数size_t courseCount = courseMap.count(101);cout << "\nkey=101的课程个数:" << courseCount << endl;  // 输出:3// 4. erase接口:按key删除所有匹配项size_t delCount = courseMap.erase(101);cout << "删除key=101的课程个数:" << delCount << endl;  // 输出:3cout << "删除后multimap:" << endl;for (const auto& kv : courseMap) {cout << "Course ID: " << kv.first << ", Name: " << kv.second << endl;}/* 输出:Course ID: 102, Name: Python*/return 0;
}

4.4 multimap 的适用场景

        multimap 因支持 key 冗余,主要适用于以下场景:

  1. 一对多映射关系:如 “课程 ID - 课程名称”(一个 ID 对应多个课程)、“用户 ID - 订单信息”(一个用户对应多个订单);
  2. 按 key 分组存储:需将相同 key 的键值对集中存储,便于批量查询;
  3. 有序重复键值对:需维持 key 的有序性,同时允许重复 key(如按时间戳存储的日志,相同时间戳对应多条日志)。

五、map 系列容器的OJ题实战

        map 系列凭借 “键值对映射” 与 “高效查找” 特性,在算法题中有着广泛应用。本节将基于一些实战案例,深入解析 map 在环形链表检测、随机链表复制、高频单词统计等场景的使用。

5.1 场景一:随机链表的复制(键值对映射解决指针关联)

题目链接:https://leetcode.cn/problems/copy-list-with-random-pointer/description/

题目描述:给定一个随机链表,每个节点除了有一个next指针指向下一个节点,还有一个random指针指向链表中的任意节点或null。请深拷贝这个链表,返回拷贝后的头节点。

难点:普通链表复制只需按next顺序拷贝,但随机链表的random指针可能指向未拷贝的节点,难以直接关联。

解题思路(基于 map)

        利用 map 的 “键值对映射” 特性,建立 “原节点 - 拷贝节点” 的一一对应关系:

  1. 第一遍遍历原链表,拷贝每个节点的val,并将{原节点, 拷贝节点}存入 map;
  2. 第二遍遍历原链表,通过 map 找到拷贝节点对应的nextrandom指针(原节点的next/random对应的拷贝节点,即map[原节点->next]/map[原节点->random]),完成拷贝链表的指针关联。
代码实现
#include <iostream>
#include <map>
using namespace std;// 随机链表节点定义
struct Node {int val;Node* next;Node* random;Node(int x) : val(x), next(nullptr), random(nullptr) {}
};class Solution {
public:Node* copyRandomList(Node* head) {if (head == nullptr) return nullptr;map<Node*, Node*> nodeMap;  // 建立原节点到拷贝节点的映射Node* copyHead = nullptr;Node* copyTail = nullptr;Node* cur = head;// 第一遍遍历:拷贝节点val,建立映射while (cur != nullptr) {Node* newNode = new Node(cur->val);nodeMap[cur] = newNode;  // 存储{原节点, 拷贝节点}// 构建拷贝链表的next指针(初步)if (copyTail == nullptr) {copyHead = copyTail = newNode;} else {copyTail->next = newNode;copyTail = copyTail->next;}cur = cur->next;}// 第二遍遍历:处理拷贝节点的random指针cur = head;Node* copyCur = copyHead;while (cur != nullptr) {// 原节点的random对应拷贝节点的randomif (cur->random != nullptr) {copyCur->random = nodeMap[cur->random];} else {copyCur->random = nullptr;}cur = cur->next;copyCur = copyCur->next;}return copyHead;}
};// 测试代码:创建随机链表并拷贝
int main() {Solution sol;// 创建原链表:1 -> 2 -> 3Node* head = new Node(1);head->next = new Node(2);head->next->next = new Node(3);// 设置random指针:1->3,2->1,3->nullhead->random = head->next->next;head->next->random = head;head->next->next->random = nullptr;// 拷贝链表Node* copyHead = sol.copyRandomList(head);// 验证拷贝结果Node* cur = copyHead;cout << "拷贝链表节点及random:" << endl;while (cur != nullptr) {cout << "Node val: " << cur->val;if (cur->random != nullptr) {cout << ", Random val: " << cur->random->val << endl;} else {cout << ", Random: null" << endl;}cur = cur->next;}/* 输出(与原链表一致):Node val: 1, Random val: 3Node val: 2, Random val: 1Node val: 3, Random: null*/// 释放内存(简化处理)delete head->next->next;delete head->next;delete head;delete copyHead->next->next;delete copyHead->next;delete copyHead;return 0;
}
复杂度分析
  • 时间复杂度:O (n),其中 n 为链表长度,两次遍历均为 O (n),map 插入与查找均为 O (log n),但整体由遍历主导;
  • 空间复杂度:O (n),用于存储 map 中的 n 个节点映射。

5.2 场景二:前 K 个高频单词(map 统计 + 排序)

题目链接:https://leetcode.cn/problems/top-k-frequent-words/description/

题目描述:给定一个单词列表,返回前 K 个出现频率最高的单词。若多个单词频率相同,则按字典序排序(字典序小的在前)。

解题思路

  1. 用 map 统计每个单词的出现频率(利用operator[]简化统计);
  2. 将 map 中的键值对转换为 vector,按 “频率降序、字典序升序” 排序;
  3. 取排序后的前 K 个单词作为结果。
代码实现
#include <iostream>
#include <vector>
#include <map>
#include <algorithm>
#include <string>
using namespace std;class Solution {
public:// 自定义排序规则:先按频率降序,再按字典序升序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<string> topKFrequent(vector<string>& words, int k) {// 1. 用map统计单词频率(map默认按key字典序排序,便于后续频率相同时的排序)map<string, int> countMap;for (const auto& word : words) {countMap[word]++;  // 统计频率,key自动按字典序排序}// 2. 将map转换为vector,便于排序vector<pair<string, int>> freqVec(countMap.begin(), countMap.end());// 3. 按自定义规则排序sort(freqVec.begin(), freqVec.end(), Compare());// 4. 取前K个单词作为结果vector<string> result;for (int i = 0; i < k; ++i) {result.push_back(freqVec[i].first);}return result;}
};// 测试代码
int main() {Solution sol;vector<string> words = {"i", "love", "leetcode", "i", "love", "coding"};int k = 2;vector<string> topK = sol.topKFrequent(words, k);cout << "前" << k << "个高频单词:" << endl;for (const auto& word : topK) {cout << word << " ";}cout << endl;  // 输出:i love return 0;
}
复杂度分析
  • 时间复杂度:O (n log n),其中 n 为单词总数。map 统计频率为 O (n log m)(m 为不同单词数),排序为 O (m log m),整体为 O (n log n);
  • 空间复杂度:O (m),用于存储 map 和 vector。

总结

        map 系列是 C++ STL 中用于键值对存储的核心容器,通过红黑树实现了高效的增删查改操作,通过 “key 唯一”(map)与 “key 冗余”(multimap)的特性覆盖了不同场景的需求。掌握 map 系列的使用,能大幅提升代码的结构化程度与效率,尤其在字典查询、数据统计、缓存映射等场景中不可或缺。在实际开发中,需根据 “是否允许 key 冗余”“是否需要有序”“查找效率要求” 等因素,选择 map、multimap 或 unordered_map,让工具更好地服务于需求。

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

相关文章:

  • C++之lambda表达式使用解读
  • kanass零基础学习,如何做好测试管理
  • 五、RPA案例:起点中文网强推小说自动化保存
  • 【2025最新】04 Spring Boot 构建 RESTful Web 服务
  • ComfyUI资源网: 一站式 ComfyUI 工具与学习社区
  • 泉州网站建设网络推广网站超市系统 源码
  • IOT——STM32F031K6U6+RS485+BMP280
  • 怎么才能实现网站HTTPS访问?
  • 供应链安全:数字时代的关键防线
  • 冰雹气候特征
  • 云服务器安装jdk——超详细
  • 【Linux】进程间通信(一)匿名管道原理剖析与进程池手动实现全流程
  • 【Qt开发】布局管理器(三)-> QGridLayout 网格布局
  • *绿色新华-生态金融*系统程序方案
  • 新乡百度关键词优化外包seo网站关键词优化排名
  • wordpress搭建商城百度关键词优化手段
  • Stirling-PDF合并拆分加密样样行:cpolar内网穿透实验室第563个成功挑战
  • vue 后端nodejsexpress-generator 脚手架环境搭建
  • 市政二级总承包资质承包范围seo软件排行榜前十名
  • 网站设计奖wordpress媒体库搜索
  • 代码随想录训练营打卡Day37| 动态规划part05
  • 实战代码解析:拼多多 item_search API 使用教程
  • 基于栅格地图的遗传算法路径规划MATLAB实现
  • 公司网站二维码生成器网络营销ppt讲解
  • Java中Stream使用示例-对实体List分组且保留原数据顺序并对分组后的每组内的数据进行部分业务逻辑修改操作
  • 大型购物网站建设方案wordpress登录页面模板下载
  • CSS 选择器详解
  • MySQL的DATE_ADD函数详解
  • 请求头是什么
  • 【EMNLP2025】阿里云人工智能平台PAI多篇论文入选EMNLP2025