std::unordered_set(C++)
std::unorderd_set
- 1. 概述
- 定义
- 特点
- 2. 内部实现
- 3. 性能特征
- 4. 常用 API
- 5. 使用示例
- 6. 自定义哈希与比较
- 7. 注意事项与优化
- 8. 使用建议
1. 概述
定义
template<class Key,class Hash = std::hash<Key>,class KeyEqual = std::equal_to<Key>,class Allocator = std::allocator<Key>
>
class std::unordered_set;
特点
- 哈希表:基于哈希表实现,平均具有 O(1) 的查找、插入和删除效率。
- 无序:元素在桶(bucket)中按哈希值分布,不保证遍历顺序。
- 唯一:集合中不允许重复元素;插入已存在的键,不会改变容器状态。
2. 内部实现
-
桶与冲突解决
- 容器维护若干桶,每个桶内用链表(或单链/双链)存放哈希冲突的元素。
- 元素落桶位置:bucket_index = hash(key) % bucket_count。
-
装载因子(load factor)
- 定义:load_factor = size() / bucket_count。
- 达到或超过 max_load_factor() 时,自动 rehash(扩容并重新分布)。
-
再哈希(rehash)与保留(reserve)
- rehash(n):将桶数至少调整为 n,并重分配元素。
- reserve(n):保证能存放 n 个元素而不触发自动 rehash。
3. 性能特征
操作 | 平均复杂度 | 最坏情况复杂度 | 说明 |
---|---|---|---|
find | O(1) | O(n) | 当所有元素落入同一桶时退化为链表扫描 |
insert | O(1) | O(n) | 可能触发 rehash(O(n)) |
erase(key) | O(1) | O(n) | 同上 |
clear | O(n) | O(n) | |
遍历 | O(n + b) | O(n + b) | b = bucket_count;需跳过空桶 |
- 空间开销:
- 每个桶存储一个指针(头结点);每个元素节点存储键值与下一个节点指针。
- 哈希函数质量:
- 优质哈希函数能减少冲突,提升最坏情况表现;可针对自定义类型提供专业哈希。
4. 常用 API
// 构造与析构
std::unordered_set<Key> s1; // 默认构造
std::unordered_set<Key> s2(100); // 指定初始桶数
std::unordered_set<Key> s3(100, MyHash{}); // 指定哈希函数
std::unordered_set<Key> s4(100, MyHash{}, MyEqual{});// 指定哈希与相等比较// 大小与容量
bool empty() const noexcept;
size_t size() const noexcept;
size_t bucket_count() const noexcept;
float load_factor() const noexcept;
float max_load_factor() const noexcept;
void rehash(size_t n);
void reserve(size_t n);// 插入
std::pair<iterator,bool> insert(const Key& key);
template<class... Args>
std::pair<iterator,bool> emplace(Args&&... args);// 查找与删除
iterator find(const Key& key);
size_t erase(const Key& key);
void clear();// 遍历
iterator begin() noexcept;
iterator end() noexcept;
const_iterator cbegin() const noexcept;
const_iterator cend() const noexcept;// 桶信息
size_t bucket(const Key& key) const noexcept;
size_t bucket_size(size_t n) const noexcept;
Hash hash_function() const noexcept;
KeyEqual key_eq() const noexcept;
5. 使用示例
#include <unordered_set>
#include <string>
#include <iostream>int main() {// 1. 创建并插入std::unordered_set<std::string> us;us.insert("apple");us.insert(std::string("banana"));us.emplace("cherry"); // 2. 查找if (us.find("banana") != us.end()) {std::cout << "Found banana\n";}// 3. 遍历(无序)for (auto& s : us) {std::cout << s << "\n";}// 4. 预分配桶数us.reserve(1000); // 预计要插入 1000 个元素,减少 rehash// 5. 输出装载因子std::cout << "Load factor: " << us.load_factor() << "\n";return 0;
}
6. 自定义哈希与比较
对于自定义类型,需提供哈希函数和相等比较:
struct Point { int x, y; };// 自定义哈希:结合位运算
struct PointHash {size_t operator()(Point const& p) const noexcept {return std::hash<int>()(p.x) ^ (std::hash<int>()(p.y) << 1);}
};// 自定义相等比较
struct PointEqual {bool operator()(Point const& a, Point const& b) const noexcept {return a.x == b.x && a.y == b.y;}
};std::unordered_set<Point, PointHash, PointEqual> pts;
7. 注意事项与优化
-
避免频繁 rehash
- 通过 reserve(n) 预先分配,避免多次自动扩容带来的性能抖动。
-
遍历顺序不稳定
- 迭代顺序可能因 rehash 而变化,不要依赖元素插入或桶索引顺序。
-
哈希安全
- 对抗恶意输入可使用带随机种子或高质量哈希(如 MurmurHash、CityHash)。
-
线程安全
- 并发读一般安全;读写或多写需自行加锁。
-
内存 vs 性能
- 哈希表占用内存高于平衡树;在对空间敏感场景须权衡使用。
8. 使用建议
-
适用场景
- 快速查找/去重:不关心顺序,关注高效查找/插入/删除时首选。
- 元素唯一:需要自动去重功能的集合场景。
-
不适用场景
- 有序遍历或范围查询:需保序或区间操作时,改用 std::set 或 std::map。
- 重复键:若允许多值,使用 std::unordered_multiset。