C++ unordered_multiset
简介
unordered_multiset
是C++ STL(标准模板库)中的一个容器,它允许存储重复的键值,并且以无序的方式组织元素。该容器在C++11标准中被引入,它是基于哈希表实现的,因此提供了接近O(1)的平均查找、插入和删除时间复杂度。
与 std::multiset
相比,unordered_multiset
不会对元素进行排序,因此在不需要元素有序的情况下,它通常能提供更好的性能。
- 允许重复键值
- 无序存储(基于哈希表)
- 快速查找、插入和删除操作
- 需要哈希函数支持
头文件和命名空间
在使用 unordered_multiset
之前,需要包含相应的头文件:
#include <unordered_set> // 注意:unordered_multiset 定义在 unordered_set 头文件中
unordered_multiset
位于 std
命名空间内,所以使用时需要:
std::unordered_multiset<int> myMultiset; // 示例声明
或者使用 using
指令:
using namespace std;
unordered_multiset<int> myMultiset;
创建和初始化
基本创建方式
// 创建一个空的unordered_multiset
std::unordered_multiset<int> nums1;
// 使用初始化列表创建
std::unordered_multiset<int> nums2 = {1, 2, 3, 2, 1};
// 使用迭代器范围创建
std::vector<int> vec = {5, 6, 7, 5};
std::unordered_multiset<int> nums3(vec.begin(), vec.end());
// 拷贝构造
std::unordered_multiset<int> nums4(nums2);
自定义哈希函数和比较器
对于自定义类型,需要提供哈希函数和相等比较器:
struct Person {
std::string name;
int age;
bool operator==(const Person& other) const {
return name == other.name && age == other.age;
}
};
// 自定义哈希函数
struct PersonHash {
size_t operator()(const Person& p) const {
return std::hash<std::string>()(p.name) ^ std::hash<int>()(p.age);
}
};
// 创建使用自定义哈希函数的unordered_multiset
std::unordered_multiset<Person, PersonHash> people;
基本操作
插入元素
std::unordered_multiset<int> nums;
// 插入单个元素
nums.insert(10);
nums.insert(20);
nums.insert(10); // 可以插入重复值
// 插入迭代器范围的元素
std::vector<int> vec = {30, 40, 30};
nums.insert(vec.begin(), vec.end());
// 使用emplace直接构造并插入元素(效率更高)
nums.emplace(50);
查找元素
std::unordered_multiset<int> nums = {1, 2, 3, 2, 1};
// 查找元素
auto it = nums.find(2);
if (it != nums.end()) {
std::cout << "找到元素: " << *it << std::endl;
}
// 统计元素出现次数
std::cout << "元素2出现的次数: " << nums.count(2) << std::endl;
// 检查元素是否存在
if (nums.contains(3)) { // C++20特性
std::cout << "元素3存在" << std::endl;
}
// 查找特定值的所有实例
auto range = nums.equal_range(2);
for (auto it = range.first; it != range.second; ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
删除元素
std::unordered_multiset<int> nums = {1, 2, 3, 2, 1};
// 删除特定值的一个实例
nums.erase(nums.find(1));
// 删除特定值的所有实例
nums.erase(2);
// 使用迭代器范围删除
auto range = nums.equal_range(1);
nums.erase(range.first, range.second);
// 清空容器
nums.clear();
容量操作
std::unordered_multiset<int> nums = {1, 2, 3, 2, 1};
// 检查容器是否为空
if (!nums.empty()) {
std::cout << "容器不为空" << std::endl;
}
// 获取元素数量
std::cout << "元素数量: " << nums.size() << std::endl;
// 获取容器当前桶数
std::cout << "桶数量: " << nums.bucket_count() << std::endl;
// 获取负载因子(load factor),即元素数量/桶数量
std::cout << "负载因子: " << nums.load_factor() << std::endl;
迭代和遍历
std::unordered_multiset<int> nums = {1, 2, 3, 2, 1};
// 使用迭代器遍历
for (auto it = nums.begin(); it != nums.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 使用范围for循环(C++11及之后)
for (const auto& num : nums) {
std::cout << num << " ";
}
std::cout << std::endl;
unordered_multiset
中元素的遍历顺序不是固定的,而且可能在插入或删除操作后发生变化。这是因为内部哈希表可能需要重新分配以保持性能。
哈希表操作
unordered_multiset
提供了一些操作其底层哈希表的方法:
std::unordered_multiset<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 调整桶的数量
nums.rehash(20); // 设置至少20个桶
nums.reserve(100); // 预留足够的空间存储100个元素
// 查看特定桶的信息
for (size_t i = 0; i < nums.bucket_count(); ++i) {
std::cout << "桶 " << i << " 包含 " << nums.bucket_size(i) << " 个元素" << std::endl;
}
// 获取元素所在的桶
std::cout << "元素5在桶 " << nums.bucket(5) << " 中" << std::endl;
性能考虑
unordered_multiset
的性能特点:
当存在大量哈希冲突时,unordered_multiset
的性能会从O(1)劣化至O(n)。选择合适的哈希函数和负载因子对于维持良好性能至关重要。
实际应用场景
计数问题
使用 unordered_multiset
统计字符出现频率:
#include <iostream>
#include <unordered_set>
#include <string>
int main() {
std::string text = "hello world hello cpp";
std::unordered_multiset<char> characters;
// 插入所有字符
for (char c : text) {
characters.insert(c);
}
// 输出每个字符的频率
std::cout << "字符频率统计:" << std::endl;
for (char c = 'a'; c <= 'z'; ++c) {
size_t count = characters.count(c);
if (count > 0) {
std::cout << c << ": " << count << std::endl;
}
}
return 0;
}
输出示例:
字符频率统计:
c: 1
d: 1
e: 2
h: 2
l: 5
o: 3
p: 2
r: 1
w: 1
多值映射关系
记录学生选修的多门课程:
#include <iostream>
#include <unordered_set>
#include <string>
int main() {
std::unordered_multiset<std::string> courseEnrollments;
// 添加学生选课记录
courseEnrollments.insert("Math");
courseEnrollments.insert("Physics");
courseEnrollments.insert("Chemistry");
courseEnrollments.insert("Math"); // 多个学生选了数学
courseEnrollments.insert("Physics"); // 多个学生选了物理
courseEnrollments.insert("Biology");
courseEnrollments.insert("Math"); // 又一个学生选了数学
// 显示每门课程的学生数量
std::cout << "课程注册统计:" << std::endl;
std::cout << "数学: " << courseEnrollments.count("Math") << " 名学生" << std::endl;
std::cout << "物理: " << courseEnrollments.count("Physics") << " 名学生" << std::endl;
std::cout << "化学: " << courseEnrollments.count("Chemistry") << " 名学生" << std::endl;
std::cout << "生物: " << courseEnrollments.count("Biology") << " 名学生" << std::endl;
std::cout << "计算机: " << courseEnrollments.count("Computer Science") << " 名学生" << std::endl;
return 0;
}
输出示例:
课程注册统计:
数学: 3 名学生
物理: 2 名学生
化学: 1 名学生
生物: 1 名学生
计算机: 0 名学生
unordered_multiset
vs 其他容器
让我们比较 unordered_multiset
与相关容器的区别:
特性 | unordered_multiset | multiset | unordered_set | set |
---|---|---|---|---|
允许重复值 | ✅ | ✅ | ❌ | ❌ |
元素排序 | ❌ | ✅ | ❌ | ✅ |
查找复杂度 | 平均O(1) | O(log n) | 平均O(1) | O(log n) |
内部实现 | 哈希表 | 红黑树 | 哈希表 | 红黑树 |
迭代器类型 | 前向迭代器 | 双向迭代器 | 前向迭代器 | 双向迭代器 |
总结
unordered_multiset
是C++ STL中一个强大的容器,适用于需要存储重复元素且不关心顺序,但需要高效查找、插入和删除操作的场景。它的主要特点包括:
- 基于哈希表实现,提供平均常数时间复杂度的操作
- 允许存储重复键值
- 元素无序存储
- 支持自定义哈希函数和比较器
这些特性使它在处理需要多值关联的数据集、频率统计等问题上表现出色。然而,在需要排序数据或内存开销敏感的场景中,可能需要考虑其他容器类型。
练习
- 创建一个
unordered_multiset
并计算给定文本中每个单词的出现频率。 - 实现一个简单的图结构,使用
unordered_multiset
存储边的信息。 - 编写一个程序,比较
unordered_multiset
和multiset
在大数据集上的性能差异。 - 创建一个自定义类型并实现合适的哈希函数,然后使用
unordered_multiset
存储该类型。 - 使用
unordered_multiset
实现一个简单的文本索引系统,根据关键词快速查找包含该词的所有文档。
延伸阅读
- C++ 标准库中的哈希函数如何工作
- 哈希冲突的解决策略与性能影响
- 选择合适的容器:何时使用
unordered_multiset
而非其他容器 - 自定义哈希函数的最佳实践
- C++20中
unordered_multiset
的新特性