std::unordered_set
std::unordered_set 是一个无序的关联容器,它与 std::set 的主要区别在于元素的存储顺序。std::set 按照元素的升序排列,而 std::unordered_set 不保持任何顺序,它依赖于哈希表实现,因此元素的存储位置是由哈希函数决定的。std::unordered_set 提供了高效的查找、插入和删除操作,底层通常使用哈希表,查找、插入和删除操作的平均时间复杂度为 O(1),但最坏情况下为 O(n)。
unordered_map是一个模板类,其定义如下:
std::unordered_set<Key, Hash = std::hash<Key>, Pred = std::equal_to<Key>, Alloc = std::allocator<Key>>
- Key 是存储在
unordered_set中的元素类型。 - Hash 是一个函数或函数对象,用于生成元素的哈希值,默认为
std::hash<Key>。 - Pred 是一个二元谓词,用于比较两个元素是否相等,默认为
std::equal_to<Key>。 - Alloc 是分配器类型,用于管理内存分配,默认为
std::allocator<Key>。
1. 引入
#include <unordered_set>
2. 存储方式
std::unordered_set 使用哈希表(hash table)来存储元素。每个元素的存储位置由哈希函数计算出来,因此它不保证元素的顺序。哈希表允许非常高效的查找、插入和删除操作,通常时间复杂度为 O(1),但在哈希冲突的情况下,最坏情况的时间复杂度可能退化为 O(n)。
- 插入:插入一个元素时,哈希函数会计算该元素的哈希值,并将元素存储在哈希表中。
- 查找:查找操作的时间复杂度为 O(1),但是哈希冲突可能导致时间复杂度退化为 O(n)。
- 删除:删除操作的时间复杂度通常为 O(1),但最坏情况下可能退化为 O(n)。
std::unordered_set 与 std::set 最大的区别是,它使用哈希表来存储元素,因此没有顺序性,无法进行按顺序的遍历。
3. 方法
(1)构造方法
-
unordered_set(): 创建一个空的std::unordered_set,默认构造函数。std::unordered_set<int> us; std::unordered_set<int> us = {1, 2, 3, 4, 5}; -
unordered_set(const unordered_set&): 复制构造函数,创建一个与另一个std::unordered_set完全相同的副本。std::unordered_set<int> us1 = {1, 2, 3, 4, 5}; std::unordered_set<int> us2(us1); // us2 是 us1 的一个复制版本 -
unordered_set(begin, end): 使用另一个容器或迭代器区间中的元素初始化std::unordered_set。std::vector<int> vec = {1, 2, 3, 4, 5}; std::unordered_set<int> us(vec.begin(), vec.end()); // 复制 vector 的元素到 unordered_set
(2) 大小函数
-
size_t size() const: 返回std::unordered_set中元素的个数。std::unordered_set<int> us = {1, 2, 3, 4}; std::cout << us.size(); // 输出 4 -
bool empty() const: 检查std::unordered_set是否为空,若为空返回true,否则返回false。std::unordered_set<int> us; if (us.empty()) { std::cout << "Unordered Set is empty." << std::endl; } -
size_t max_size() const: 返回最大可允许的unordered_set元素数量。 -
void reserve(size_t n): 用于预先分配哈希表的桶数量。这可以有效减少动态扩容的开销,特别是在你知道将要插入大量元素时。
(3) 增加函数
-
std::pair<iterator, bool> insert(const T& value): 插入元素到unordered_set中,返回一个pair,其中first是指向插入元素所在位置的迭代器,second是插入是否成功(如果存在重复元素,则插入失败)。us.insert({1, 2, 3}); // initializer_list us.insert(us.begin(), 42); // 带 hint 的插入 us.insert(vec.begin(), vec.end()); // 区间插入 -
std::pair<iterator, bool> emplace(_Args&&... args): 就地构建并插入元素。 -
iterator emplace_hint(const_iterator hint, _Args&&... args): 就地构建并插入元素,且指定插入位置,但是如果指定位置错误会发生重排序,最坏情况下可能会降低插入的效率。在哈希索引的前提下是无序的,因此该插入操作等效于insert。
(4) 删除元素
-
void clear(): 删除std::unordered_set中的所有元素,使容器变为空。std::unordered_set<int> us = {1, 2, 3, 4}; us.clear(); // 删除所有元素,us 变为空 {} -
void erase(iterator pos): 删除指定位置pos处的元素。std::unordered_set<int> us = {1, 2, 3, 4}; us.erase(std::next(us.begin(), 2)); // 删除索引为2的元素,us 变为 {1, 2, 4} -
size_t erase(const key_type& key): 删除指定键,返回删除个数。 -
erase(first, last): 删除迭代器区间。
(5) 查找函数
-
iterator find(const T& value): 查找某元素在unordered_set中的位置,返回一个指向该元素的迭代器,如果找不到会返回end()。std::unordered_set<int> us = {1, 2, 3, 4}; auto it = us.find(1); // 返回指向键1的迭代器 if (it != us.end()) { std::cout << *it << std::endl; // 输出: 1 } -
std::pair<const_iterator, const_iterator> equal_range(const key_type& __x) const: 返回一对迭代器,表示所有具有等于k的键的元素的范围。由于set包含唯一元素,因此下界将是元素本身,上限将指向键k之后的下一个元素。如果没有与键K匹配的元素,则根据容器的内部比较对象(key_comp),返回的范围的长度为0,两个迭代器均指向大于k的第一个元素。如果键超过了set容器中的最大元素,它将返回一个指向set容器中最后一个元素的迭代器。 -
size_t count(const T& value): 返回元素是否存在(0 或 1)。 -
bool contains(const T& value) const(C++20):检查是否包含该元素。
(6) 遍历函数
-
iterator begin(): 返回指向std::unordered_set第一个元素的迭代器。 -
iterator end(): 返回指向std::unordered_set最后一个元素之后位置的迭代器。 -
使用基于范围的
for循环(C++11 及以上): 直接遍历std::unordered_set中的每个元素。std::unordered_set<int> us = {1, 2, 3}; for (const int& num : us) { std::cout << num << " "; // 输出: 1 2 3 } -
使用传统的
for循环(基于迭代器): 使用迭代器来遍历std::unordered_set。std::unordered_set<int> us = {1, 2, 3}; for (auto it = us.begin(); it != us.end(); ++it) { std::cout << *it << " "; // 输出: 1 2 3 }
(7)哈希相关函数
-
size_type max_bucket_count() const noexcept: max_bucket_count() 返回 std::unordered_set 中可用的最大哈希桶数量。哈希表是由一组桶(bucket)构成的,每个桶存储一定数量的元素。当元素的数量增加时,std::unordered_set 可能会自动扩展桶的数量,以减少哈希冲突。max_bucket_count() 允许你查看哈希表最多能支持多少个桶。 -
float max_load_factor() const noexcept: max_load_factor() 返回或设置哈希表的最大负载因子。负载因子是哈希表中元素的数量与桶数量之比。它决定了哈希表何时扩展——当负载因子超过最大值时,std::unordered_set 会增加桶的数量,减少哈希冲突,从而提高性能。- 负载因子是指:负载因子 = 元素个数 / 桶的数量
- 最大负载因子是哈希表在扩展之前允许的最大负载因子。负载因子越高,意味着桶的数量相对较少,这可能导致更多的哈希冲突,降低查找效率。
-
void max_load_factor(float __z): 设置最大负载因子。 -
size_t bucket_count() const: 返回当前 unordered_set 的桶数量。哈希表的桶数量与容器中元素的数量以及负载因子相关,通常哈希表会在元素数量增加时自动扩展桶的数量。 -
size_t bucket_size(size_t n) const: 返回指定桶的元素数量。哈希表中的每个桶可能包含多个元素,特别是当哈希冲突发生时,多个元素可能会存储在同一个桶中。 -
size_t bucket(const key_type& key) const: 返回给定键所在的桶的索引。这个函数对于理解元素是如何分布在哈希表中的非常有用。 -
float load_factor() const: 返回当前哈希表的负载因子,即元素的数量与桶数量的比值。负载因子越高,哈希冲突的可能性越大,因此负载因子对于性能非常重要。 -
void rehash(size_t n): 用来调整哈希表中的桶数量。它的作用是根据新的元素数量来调整桶的数量,避免哈希冲突过多。rehash() 会导致重新分配桶并重新哈希元素。n 是希望容器能够容纳的元素数量。
(8) 其他函数
-
swap(unordered_set& other): 交换当前std::unordered_set和另一个std::unordered_set的内容。 -
std::allocator<T> get_allocator() const: 返回容器所使用的分配器(allocator)。 -
void merge(unordered_set& other):merge会将另一个 unordered_set 中不与当前容器重复的元素“移动”过来。std::unordered_set<int> s1 = {1, 3, 5}; std::unordered_set<int> s2 = {2, 4, 6}; s1.merge(s2); // s1 合并 s2 后,变为 {1, 2, 3, 4, 5, 6} -
key_equal_type key_eq() const: 返回一个比较器,用于判断两个键是否相等std::unordered_set<Person, PersonHash> people; Person p1 = {"Alice", 30}; Person p2 = {"Bob", 25}; people.insert(p1); people.insert(p2); // 使用 key_eq 获取比较函数 auto eq = people.key_eq(); // 使用 key_eq 函数比较两个键 std::cout << "Are p1 and p2 equal? " << std::boolalpha << eq(p1, p2) << std::endl;