Skip to main content
Version: 1.0

multiset

std::multiset 是 C++ 标准库中的有序关联容器,与 std::set 类似,但允许存储重复的键。它基于平衡二叉搜索树实现,元素始终按键的排序规则保持有序。

当你需要一个有序集合,且同一元素可能出现多次时,multiset 是天然的选择。

头文件与基本特征

使用 multiset 时,需要包含头文件:

#include <set>

常见特征如下:

特征说明
键是否允许重复是,允许多个相等键
是否有序是,按键的排序规则排序
底层结构通常为平衡二叉搜索树
插入复杂度O(log n)
查找复杂度O(log n)
删除复杂度O(log n) 单个,区间删除视情况而定
迭代器失效删除对应元素时失效,插入不失效

基本用法

#include <iostream>
#include <set>

int main() {
std::multiset<int> ms;

ms.insert(5);
ms.insert(2);
ms.insert(5); // 重复元素可以插入
ms.insert(8);
ms.insert(2);

for (std::multiset<int>::iterator it = ms.begin(); it != ms.end(); ++it) {
std::cout << *it << ' ';
}
// 输出:2 2 5 5 8(按升序排列)

return 0;
}

插入重复元素时,multiset 会将它们都保留。等值元素的顺序为插入顺序(C++98/03 未严格规定,实践中通常追加在已有等值元素之后)。

常用成员函数

函数作用
insert(x)插入元素,返回迭代器(非 pair,和 set 不同)
erase(x)删除所有等于 x 的元素,返回删除数量
erase(it)删除迭代器指向的元素
find(x)查找等于 x 的元素,返回其中一个的迭代器
count(x)返回等于 x 的元素数量
lower_bound(x)第一个不小于 x 的元素
upper_bound(x)第一个大于 x 的元素
equal_range(x)返回等值范围 [lower_bound, upper_bound)
size()元素个数
empty()判断是否为空
clear()清空

需要注意:multiset::erase(x) 按值删除时会删除所有匹配元素,而不是只删一个。这和 set 的行为一致,但在 multiset 里影响更大。

要只删除一个等值元素,使用 erase(find(x))

#include <iostream>
#include <set>

int main() {
std::multiset<int> ms;
ms.insert(5);
ms.insert(5);
ms.insert(5);

// 只删除一个 5
std::multiset<int>::iterator it = ms.find(5);
if (it != ms.end()) {
ms.erase(it);
}

std::cout << ms.count(5) << '\n'; // 2
return 0;
}

查找与遍历等值范围

equal_range 用于一次获取等值范围:

#include <iostream>
#include <set>

int main() {
std::multiset<int> ms;
ms.insert(3);
ms.insert(5);
ms.insert(5);
ms.insert(5);
ms.insert(7);

typedef std::multiset<int>::iterator Iter;
std::pair<Iter, Iter> range = ms.equal_range(5);

std::cout << "count of 5: ";
int count = 0;
for (Iter it = range.first; it != range.second; ++it) {
++count;
}
std::cout << count << '\n'; // 3

return 0;
}

lower_boundupper_bound 也可以配合使用达到同样效果。

自定义排序

set 一样,multiset 支持自定义比较器:

#include <iostream>
#include <set>

struct Descending {
bool operator()(int a, int b) const {
return a > b;
}
};

int main() {
std::multiset<int, Descending> ms;

ms.insert(1);
ms.insert(3);
ms.insert(1);
ms.insert(2);

for (std::multiset<int, Descending>::iterator it = ms.begin(); it != ms.end(); ++it) {
std::cout << *it << ' ';
}
// 输出:3 2 1 1

return 0;
}

multiset vs set vs multimap

容器键唯一有序值类型
set单个键值
multiset单个键值
map键值对
multimap键值对

选择规则很简单:只需要统计或维护有序的可重复集合时,选 multiset;需要键值映射时,选 multimap

典型场景

  1. 成绩排名中存在并列名次时存储成绩。
  2. 事件时间线中同一时间可能发生多个事件。
  3. 维护一个有序的日志记录,允许相等的时间戳。
  4. 需要快速统计某个值出现次数,且数据需要保持有序。

使用注意

  1. erase(x) 按值删除会移除所有等值元素,需要仔细确认。
  2. count(x)multiset 中的复杂度是 O(log n + k)(k 为等值元素个数),等值元素极多时注意性能。
  3. 等值元素的迭代顺序未严格规定(C++98/03),不要依赖它们的相对顺序。
  4. 插入不会使迭代器失效,删除只会使被删元素的迭代器失效。

小结

std::multisetset 的可重复版本,适合需要有序存储且允许重复键的场景。通过 countequal_rangelower_bound / upper_bound 等接口,可以方便地处理等值范围。使用时要注意按值删除会清空所有匹配元素,必要时改用迭代器删除。