
在 C++ 标准库的关联容器中,multimap 是一种特殊的存在。它允许键(Key)重复,能够存储多个具有相同键的键值对,同时保持键的有序性。这种特性使得 multimap 在处理 “一对多” 关系(如课程与学生、标签与文章)时高效且便捷。
multimap 是 map 的 “兄弟” 容器,二者的核心区别在于:
map 要求键唯一,multimap 允许键重复map 插入重复键时会被忽略,multimap 会直接插入新元素multimap 不提供 operator[](因键不唯一,无法直接通过键访问唯一值)典型应用场景:
multimap 与 map 一样,底层基于 红黑树(Red-Black Tree) 实现。红黑树是平衡二叉搜索树,保证插入、删除、查找的时间复杂度均为 O(log n)。与 map 的区别在于,multimap 允许红黑树节点存储相同键的元素,插入时不会检查键是否已存在,而是直接按顺序插入到合适位置(保持中序遍历的有序性)。
红黑树节点结构示意图:

template <
class Key,
class T,
class Compare = std::less<Key>,
class Allocator = std::allocator<std::pair<const Key, T>>
> class multimap;容器 | 键唯一性 | 有序性 | 底层结构 | 查找时间 | 典型场景 |
|---|---|---|---|---|---|
map | 唯一 | 有序 | 红黑树 | O(log n) | 一对一映射 |
multimap | 允许重复 | 有序 | 红黑树 | O(log n) | 一对多映射(有序) |
unordered_map | 唯一 | 无序 | 哈希表 | O (1)(平均) | 高速查找(无需顺序) |
unordered_multimap | 允许重复 | 无序 | 哈希表 | O (1)(平均) | 高速一对多映射(无序) |
#include <iostream>
#include <map>
#include <string>
// 基本定义:键为 int,值为 string
std::multimap<int, std::string> mm;
// 初始化方式 1:插入多个键值对
std::multimap<int, std::string> scores = {
{100, "Alice"},
{100, "Bob"}, // 键重复,合法
{200, "Charlie"}
};
// 初始化方式 2:通过迭代器范围
std::multimap<int, std::string> copyScores(scores.begin(), scores.end());multimap 提供多种插入方式,均允许键重复:
方式一:insert 函数
// 插入单个键值对(推荐)
mm.insert(std::make_pair(300, "David"));
mm.insert({300, "Eve"}); // C++11 统一初始化语法
// 插入另一个 multimap 的元素
mm.insert(copyScores.begin(), copyScores.end());方式二:emplace 就地构造
// 直接构造对象,避免临时对象拷贝
mm.emplace(400, "Frank");由于键可能重复,multimap 的查找需返回键的范围:
方法一:find 查找第一个匹配键
auto it = mm.find(100);
if (it != mm.end()) {
std::cout << "First entry for key 100: " << it->second << std::endl; // 输出 Alice
}方法二:equal_range 查找键的完整范围
auto range = mm.equal_range(100);
for (auto it = range.first; it != range.second; ++it) {
std::cout << "Key 100 value: " << it->second << std::endl;
}
// 输出:
// Key 100 value: Alice
// Key 100 value: Bob方法三:lower_bound 和 upper_bound
auto lower = mm.lower_bound(100); // 第一个 >= 100 的键
auto upper = mm.upper_bound(100); // 第一个 > 100 的键
for (auto it = lower; it != upper; ++it) {
// 遍历键为 100 的所有元素
}删除指定键的所有元素
size_t count = mm.erase(100); // 返回删除的元素个数(此处为 2)
std::cout << "Erased " << count << " entries for key 100" << std::endl;删除单个元素(通过迭代器)
auto it = mm.find(200);
if (it != mm.end()) {
mm.erase(it); // 删除键为 200 的第一个元素
}// 正向遍历(按键的升序排列)
for (const auto& entry : mm) {
std::cout << entry.first << ": " << entry.second << std::endl;
}
// 反向遍历(按键的降序排列)
for (auto it = mm.rbegin(); it != mm.rend(); ++it) {
std::cout << it->first << ": " << it->second << std::endl;
}操作 | 平均复杂度 | 最坏复杂度 |
|---|---|---|
插入(insert) | O(log n) | O(n) |
删除(erase) | O(log n) | O(n) |
查找(find) | O(log n) | O(n) |
注意:红黑树的性能在极端情况下可能退化为O(n),但通过自动平衡机制,这种情况发生的概率极低。
对比其他容器:
默认情况下,multimap 使用 std::less<Key> 进行键的排序。若需自定义排序规则(如降序、自定义结构体比较),需在声明时指定比较函子。
示例:降序排序
struct DescCompare {
bool operator()(int a, int b) const {
return a > b; // 降序排列
}
};
std::multimap<int, std::string, DescCompare> reversedMm;
reversedMm.insert({100, "A"});
reversedMm.insert({200, "B"});
// 遍历顺序:200, 100示例:结构体键的比较
struct Student {
int grade;
std::string name;
// 自定义比较:先按成绩降序,同成绩按姓名升序
bool operator<(const Student& other) const {
if (grade != other.grade) {
return grade > other.grade;
}
return name < other.name;
}
};
// 使用 Student 作为键类型
std::multimap<Student, int> studentScores;
studentScores.insert({{90, "Alice"}, 1001});
studentScores.insert({{90, "Bob"}, 1002}); // 键相同,允许插入multimap 的核心价值在于高效存储同一键的多个值,典型场景如下:
场景:课程与学生管理
// 课程编号为键,学生姓名为值
std::multimap<int, std::string> courseStudents;
// 插入数据:课程 101 有多个学生
courseStudents.insert({101, "Alice"});
courseStudents.insert({101, "Bob"});
courseStudents.insert({102, "Charlie"});
// 查询课程 101 的所有学生
auto [lower, upper] = courseStudents.equal_range(101);
std::cout << "Course 101 students:" << std::endl;
for (auto it = lower; it != upper; ++it) {
std::cout << "- " << it->second << std::endl;
}insert 插入迭代器范围或初始化列表,减少红黑树旋转次数
reserve 预分配空间(虽然红黑树无容量概念,但可减少重新分配次数)
const 类型,修改键需先删除旧元素再插入新元素
当需要 “键 - 值列表” 映射时,可使用 map<Key, vector<Value>>,但 multimap 在插入时更便捷:
// 使用 map + vector 实现一对多
std::map<int, std::vector<std::string>> mapWithVector;
mapWithVector[100].push_back("Alice");
mapWithVector[100].push_back("Bob"); // 需要手动管理 vector
// 使用 multimap 更简洁
std::multimap<int, std::string> mm;
mm.insert({100, "Alice"});
mm.insert({100, "Bob"}); // 直接插入,无需预分配 vectormap 的 operator[] 通过键访问唯一值,而 multimap 中键可能对应多个值,无法通过单一值返回,因此未提供该接口。若需模拟类似功能,需手动插入:
// map 的用法(唯一值)
map[100] = "Alice"; // 若键不存在则插入,存在则覆盖
// multimap 需用 insert
mm.insert({100, "Alice"}); // 允许重复插入比较函数必须满足 严格弱序(Strict Weak Ordering):
!(a < a)a < b 且 b < c,则 a < ca < b,则 b < a 不成立错误示例(非严格弱序,导致未定义行为):
// 错误:比较函数不满足非自反性
struct BadCompare {
bool operator()(int a, int b) const { return a <= b; } // a <= b 允许 a==b,导致自反
};实现一个学生课程管理系统,支持:
#include <iostream>
#include <map>
#include <string>
int main() {
// 使用multimap存储学生-课程关系(允许重复)
std::multimap<std::string, std::string> studentCourses;
// 添加选课记录
studentCourses.insert({"Alice", "Math"});
studentCourses.insert({"Alice", "Physics"});
studentCourses.insert({"Bob", "Chemistry"});
studentCourses.insert({"Alice", "Biology"});
// 按学生查询课程
std::string targetStudent = "Alice";
auto range = studentCourses.equal_range(targetStudent);
std::cout << targetStudent << "'s courses:" << std::endl;
for (auto it = range.first; it != range.second; ++it) {
std::cout << "- " << it->second << std::endl;
}
// 按课程查询学生(需反向multimap)
std::multimap<std::string, std::string> courseStudents;
for (const auto& pair : studentCourses) {
courseStudents.insert({pair.second, pair.first});
}
std::string targetCourse = "Physics";
auto courseRange = courseStudents.equal_range(targetCourse);
std::cout << "\nStudents in " << targetCourse << ":" << std::endl;
for (auto it = courseRange.first; it != courseRange.second; ++it) {
std::cout << "- " << it->second << std::endl;
}
return 0;
}
map + vector 的嵌套结构lower_bound/upper_bound 进行高效区间操作insert 插入:避免依赖 operator[](multimap 不支持)equal_range 或 lower_bound/upper_bound 遍历所有相关元素mapunordered_multimapmultisetC++ 标准库不断进化,虽然 multimap 的接口相对稳定,但结合 Lambda 表达式可更简洁地定义比较函数(C++14 起):
// 使用 Lambda 作为比较函数(C++14+)
auto lambdaCompare = [](int a, int b) { return a > b; };
std::multimap<int, std::string, decltype(lambdaCompare)> mm(lambdaCompare);示例 1:基本操作演示
#include <iostream>
#include <map>
#include <string>
int main() {
std::multimap<int, std::string> mm;
// 插入元素
mm.insert({100, "Alice"});
mm.insert({100, "Bob"});
mm.insert({200, "Charlie"});
// 查找键 100 的所有值
auto range = mm.equal_range(100);
std::cout << "Values for key 100:" << std::endl;
for (auto it = range.first; it != range.second; ++it) {
std::cout << "- " << it->second << std::endl;
}
// 删除键 100 的所有元素
mm.erase(100);
std::cout << "Size after erase: " << mm.size() << std::endl; // 输出 1
return 0;
}
示例 2:自定义比较函数
#include <iostream>
#include <map>
struct DescCompare {
bool operator()(int a, int b) const { return a > b; }
};
int main() {
std::multimap<int, std::string, DescCompare> reversedMm;
reversedMm.insert({100, "A"});
reversedMm.insert({200, "B"});
// 反向遍历输出(实际存储顺序为 200, 100)
for (const auto& entry : reversedMm) {
std::cout << entry.first << ": " << entry.second << std::endl;
}
return 0;
}
multimap 是处理 “有序重复键” 场景的利器,其红黑树底层保证了高效的插入、删除和查找操作。通过合理使用范围查询接口和自定义比较函数,能轻松构建复杂的数据映射系统。在实际项目中,需根据键的唯一性、有序性和性能需求,灵活选择 map、multimap、unordered_map 等容器,以实现代码的高效与优雅。

using声明在模板编程中有着重要应用,如定义模板类型别名等。