C++multiset
C++ multiset 容器[编辑 | 编辑源代码]
multiset是C++标准模板库(STL)中的一个关联容器,它存储的元素按照特定排序准则自动排序,且允许存在重复元素。作为set容器的扩展版本,multiset在需要处理重复有序数据的场景中非常有用。
基本特性[编辑 | 编辑源代码]
multiset具有以下关键特性:
- 元素自动排序(默认升序)
- 允许存储重复元素
- 基于红黑树实现,提供O(log n)的查找、插入和删除操作
- 元素不可直接修改(需先删除再插入)
数学上,multiset可以表示为:
头文件与声明[编辑 | 编辑源代码]
使用multiset需要包含头文件:
#include <set>
基本声明语法:
std::multiset<数据类型> 容器名称;
构造函数[编辑 | 编辑源代码]
multiset提供多种构造函数:
构造函数 | 描述 |
---|---|
multiset<T> m |
默认构造函数 |
multiset<T> m(comp) |
使用自定义比较器 |
multiset<T> m(begin, end) |
通过迭代器范围构造 |
multiset<T> m(other) |
拷贝构造函数 |
示例:
// 默认构造
std::multiset<int> nums;
// 自定义排序(降序)
auto cmp = [](int a, int b) { return a > b; };
std::multiset<int, decltype(cmp)> descendingNums(cmp);
// 从数组初始化
int arr[] = {5, 2, 8, 2, 5};
std::multiset<int> fromArray(arr, arr + 5);
常用成员函数[编辑 | 编辑源代码]
容量相关[编辑 | 编辑源代码]
empty()
- 检查是否为空size()
- 返回元素数量max_size()
- 返回可能的最大元素数
修改操作[编辑 | 编辑源代码]
insert(const T& value)
- 插入元素erase(iterator pos)
- 删除指定位置元素erase(const T& value)
- 删除所有等于value的元素clear()
- 清空容器
查找操作[编辑 | 编辑源代码]
find(const T& value)
- 查找元素count(const T& value)
- 统计特定值出现次数lower_bound(const T& value)
- 返回第一个不小于value的迭代器upper_bound(const T& value)
- 返回第一个大于value的迭代器equal_range(const T& value)
- 返回等于value的范围
代码示例[编辑 | 编辑源代码]
基本操作示例[编辑 | 编辑源代码]
#include <iostream>
#include <set>
int main() {
std::multiset<int> scores = {85, 90, 78, 90, 82};
// 插入元素
scores.insert(88);
scores.insert(90); // 允许重复
// 遍历输出
std::cout << "All scores: ";
for(int s : scores) {
std::cout << s << " ";
}
std::cout << "\n";
// 统计特定分数出现次数
std::cout << "Number of 90s: " << scores.count(90) << "\n";
// 删除第一个90
auto it = scores.find(90);
if(it != scores.end()) {
scores.erase(it);
}
return 0;
}
输出:
All scores: 78 82 85 88 90 90 90 Number of 90s: 3
高级查找示例[编辑 | 编辑源代码]
#include <iostream>
#include <set>
int main() {
std::multiset<int> data = {10, 20, 30, 30, 30, 40, 50};
// 使用equal_range查找所有30
auto range = data.equal_range(30);
std::cout << "All 30s: ";
for(auto it = range.first; it != range.second; ++it) {
std::cout << *it << " ";
}
std::cout << "\n";
// 使用lower_bound和upper_bound
std::cout << "Elements >=25 and <=35: ";
for(auto it = data.lower_bound(25); it != data.upper_bound(35); ++it) {
std::cout << *it << " ";
}
return 0;
}
输出:
All 30s: 30 30 30 Elements >=25 and <=35: 30 30 30
实际应用案例[编辑 | 编辑源代码]
成绩统计系统[编辑 | 编辑源代码]
multiset非常适合需要处理重复有序数据的场景,如学生成绩统计:
#include <iostream>
#include <set>
#include <string>
struct Student {
std::string name;
int score;
bool operator<(const Student& other) const {
return score < other.score;
}
};
int main() {
std::multiset<Student> classA;
// 添加学生成绩(允许同分)
classA.insert({"Alice", 88});
classA.insert({"Bob", 92});
classA.insert({"Charlie", 88});
classA.insert({"David", 85});
// 输出按分数排序的学生列表
std::cout << "Students ranked by score:\n";
for(const auto& s : classA) {
std::cout << s.name << ": " << s.score << "\n";
}
// 查找88分的学生
auto range = classA.equal_range({"", 88});
std::cout << "\nStudents with 88 points:\n";
for(auto it = range.first; it != range.second; ++it) {
std::cout << it->name << "\n";
}
return 0;
}
输出:
Students ranked by score: David: 85 Alice: 88 Charlie: 88 Bob: 92 Students with 88 points: Alice Charlie
实时数据流处理[编辑 | 编辑源代码]
multiset可用于维护实时数据流的中位数:
#include <iostream>
#include <set>
class MedianTracker {
std::multiset<int> data;
public:
void addNumber(int num) {
data.insert(num);
}
double getMedian() {
if(data.empty()) return 0.0;
auto size = data.size();
auto it = data.begin();
std::advance(it, size/2);
if(size % 2 == 1) {
return *it;
} else {
auto prev = it;
--prev;
return (*prev + *it) / 2.0;
}
}
};
int main() {
MedianTracker tracker;
int stream[] = {5, 3, 7, 2, 8, 4, 9, 1, 6};
for(int num : stream) {
tracker.addNumber(num);
std::cout << "Current median: " << tracker.getMedian() << "\n";
}
return 0;
}
输出:
Current median: 5 Current median: 4 Current median: 5 Current median: 4 Current median: 5 Current median: 4.5 Current median: 5 Current median: 4.5 Current median: 5
性能分析[编辑 | 编辑源代码]
multiset的常见操作时间复杂度如下:
与相关容器比较[编辑 | 编辑源代码]
特性 | multiset | set | unordered_multiset |
---|---|---|---|
是 | 是 | 否 | |||
是 | 否 | 是 | |||
红黑树 | 红黑树 | 哈希表 | |||
O(log n) | O(log n) | O(1) | |||
排序顺序 | 排序顺序 | 无特定顺序 |
最佳实践[编辑 | 编辑源代码]
1. 选择合适的比较器:默认是升序排列,但可以通过自定义比较器改变排序方式
struct CaseInsensitiveCompare {
bool operator()(const std::string& a, const std::string& b) const {
return std::lexicographical_compare(
a.begin(), a.end(),
b.begin(), b.end(),
[](char c1, char c2) {
return tolower(c1) < tolower(c2);
});
}
};
std::multiset<std::string, CaseInsensitiveCompare> words;
2. 高效删除:当需要删除特定值的所有实例时,使用erase(val)
比循环删除更高效
3. 范围查询优化:对于连续范围查询,使用lower_bound()
和upper_bound()
组合
4. 内存考虑:multiset的每个元素都有额外开销(树节点结构),在内存敏感场景需谨慎使用
常见问题[编辑 | 编辑源代码]
Q: 为什么不能直接修改multiset中的元素? A: 直接修改会破坏内部的红黑树结构。正确做法是先删除旧元素,再插入新元素。
Q: multiset和priority_queue有什么区别? A: priority_queue只提供对最大/最小元素的快速访问,而multiset允许访问任意元素并保持全局有序。
Q: 如何获取multiset的第N个元素?
A: 使用std::next(m.begin(), N-1)
,但注意这是O(N)操作。
总结[编辑 | 编辑源代码]
multiset是C++ STL中一个强大的有序容器,特别适合需要处理重复元素且保持有序的场景。通过红黑树实现,它在插入、删除和查找操作上都能保持较好的对数时间复杂度。理解multiset的特性和正确使用方法,可以显著提高涉及有序数据处理的程序效率和简洁性。