1 概述
std::unordered_multiset 是一个无序集合,它允许存储重复的键。其内部实现通常基于哈希表,因此元素的插入、删除和查找操作的平均时间复杂度都是 O(1)。然而,需要注意的是,在最坏的情况下,这些操作的时间复杂度可能会退化到 O(n)。std::unordered_multiset 不保证元素的顺序,因此它适用于那些不需要保持元素顺序,但需要快速进行插入、删除和查找操作的场景。
std::unordered_multiset 与 std::multiset 的主要区别如下:
- 实现原理:std::unordered_multiset 基于哈希表实现,而 std::multiset 基于平衡二叉搜索树实现。
- 性能特性:std::unordered_multiset 的插入、删除和查找操作在平均情况下的时间复杂度优于std::multiset,但在最坏情况下可能会退化。而std::multiset的性能则更加稳定。
- 元素顺序:std::unordered_multiset 不保证元素的顺序,而 std::multiset 则按照键的顺序自动排序元素。
std::unordered_multiset 的实现原理主要基于哈希表。其实现原理如下:
首先,std::unordered_multiset 内部维护一个哈希表,该哈希表通常由一系列的桶(buckets)组成。每个桶实际上是一个链表或其他动态数组结构,用于存储具有相同哈希值的元素。
当向std::unordered_multiset 中插入一个元素时,首先会计算该元素的哈希值。这个哈希值是通过一个哈希函数计算得出的,该函数将元素映射到一个整数范围内。然后,根据这个哈希值,可以确定元素应该存放在哪个桶中。具体来说,哈希值通常会对桶的数量取模运算,以确定具体的桶索引。
如果计算得到的桶已经包含了一些元素,新插入的元素将被添加到该桶对应的链表或动态数组的末尾。如果桶是空的,新元素将作为该桶的第一个元素。由于 std::unordered_multiset 允许存储重复的元素,所以即使两个元素具有相同的哈希值,它们也可以被存储在同一个桶中。
当需要从 std::unordered_multiset 中查找一个元素时,同样会计算该元素的哈希值,并定位到相应的桶。然后,会在该桶的链表或动态数组中遍历,查找具有相同值的元素。由于哈希表的设计使得具有相同哈希值的元素都存储在同一个桶中,因此查找操作通常具有较高的效率。
此外,当 std::unordered_multiset 中的元素数量达到某个阈值时,通常需要进行扩容操作,以维持哈希表的性能。扩容操作会创建一个新的、更大的哈希表,并将原哈希表中的元素重新映射到新的哈希表中。扩容操作通常涉及较高的时间和空间开销,因此在设计哈希表时需要考虑扩容策略的合理性。
2 声明与初始化
2.1 声明
首先,需要包含 <unordered_set> 头文件,然后声明一个 std::unordered_multiset 变量,并指定键和值的类型。
#include <unordered_set>
std::unordered_multiset<Type> setName;
其中 Type 是元素的类型,setName 是该 unordered_multiset 变量名称。
2.2 初始化
初始化 std::unordered_multiset 可以通过多种方式进行,包括默认初始化、使用列表初始化器初始化、复制初始化、移动初始化等。
(1)默认初始化
如果没有提供任何初始化器,std::unordered_multiset 将被默认初始化,这意味着它将不包含任何元素,并且会使用默认的哈希函数和键相等性比较对象。
std::unordered_multiset<std::string> myMultiset; // 默认初始化
(2)使用列表初始化器初始化
可以使用列表初始化器(大括号 {})来初始化 std::unordered_multiset,并直接添加一些元素。
std::unordered_multiset<std::string> myMultiset = {
{
"apple"},
{
"banana"},
{
"apple"} // 注意:允许相同的元素
};
在这个例子中,myMultiset 初始时包含三个元素,其中具有相同两个元素 “apple”。
(3)复制初始化
复制初始化是通过另一个已存在的 std::unordered_multiset 来创建新的 unordered_multiset。
std::unordered_multiset<int> myMultiset1 = {
1, 2, 2, 3, 4, 4, 4};
std::unordered_multiset<int> myMultiset2(myMultiset1); // 复制初始化
(4)移动初始化
移动初始化是通过右值引用(通常是临时对象或即将被销毁的对象)来初始化新的 std::unordered_multiset。
std::unordered_multiset<int> myMultiset1 = {
1, 2, 2, 3, 4, 4, 4};
std::unordered_multiset<int> myMultiset2(std::move(myMultiset1)); // 移动初始化
// 此时 myMultiset1 的状态是未定义的,不应再使用
在实际应用中,列表初始化通常是创建和初始化 std::unordered_multiset 的最常用方式,因为它既直观又方便。复制初始化和移动初始化在需要基于现有集合创建新集合的场景中很有用,尤其是在进行函数参数传递或返回集合时。需要注意的是,移动初始化之后,原集合(在上面的例子中是 myMultiset1)的状态将变为未定义,不应再使用。
3 插入元素
insert 函数用于向 std::unordered_multiset 中插入一个或多个元素。你可以传递一个单独的元素,或者一个包含多个元素的初始化列表。
(1)插入单个元素
std::unordered_multiset<int> myMultiset;
myMultiset.insert(5); // 插入一个整数 5
myMultiset.insert(10); // 插入另一个整数 10
(2)插入多个元素
std::unordered_multiset<int> myMultiset;
myMultiset