unordered_set
C++ 哈希集合容器,仅存储唯一值,底层使用哈希表,支持快速插入与查找,元素无序,平均操作复杂度为 O(1)。
unordered_set
unordered_set
std::unordered_set
是一个 存储唯一元素 的集合容器,其特点是:
- 元素无序(内部使用哈希表)
- 不允许重复元素
- 插入、查找、删除操作的 平均时间复杂度为 O(1)
典型使用场景:
- 判断是否存在某元素(查重)
- 快速去重
- 实现集合的“集合运算”:并集、交集、差集(使用循环实现)
底层原理
哈希表结构
unordered_set
底层使用 哈希表(hash table) 实现,结构类似这样:
1
2
3
桶数组(buckets):每个 bucket 是一个链表或链式结构,称为“槽位”
哈希函数(hash_fn) -> 元素值 -> 哈希值 -> 对 bucket 数取模 -> 落入某个槽位(桶)
例如插入元素 7
:
1
bucket_index = hash(7) % bucket_count;
哈希冲突与桶
当多个元素映射到同一个 bucket 时,会在该桶中以链表/链表节点形式保存(C++ STL中常见是链表结构)。这就叫做“哈希冲突”。
例如:
1
bucket[0] -> 7 -> 17 -> 27(哈希值相同模 bucket_count)
负载因子(load factor)
负载因子 = 元素数量 / 桶数量
当负载因子超过阈值时(默认为 1.0
),会自动触发 rehash(重新分配更大的桶数组并重新映射元素)。
可以手动控制 rehash:
1
2
uset.rehash(128); // 至少分配128个桶
uset.reserve(1000); // 预留空间,提高性能(避免频繁rehash)
哈希函数与相等函数
- 默认使用
std::hash<T>
作为哈希函数 - 使用
==
判断两个元素是否相等
常用接口
构造函数
1
2
unordered_set<int> uset;
unordered_set<int> uset = {1, 2, 3, 4};
插入元素
1
uset.insert(5); // 插入 5,如果已存在则无效
查找元素
1
2
3
auto it = uset.find(5); // 如果找到返回迭代器,否则返回 uset.end()
if (it != uset.end())
cout << "Found: " << *it << endl;
删除元素
1
2
uset.erase(3); // 删除 key 为 3 的元素
uset.erase(it); // 删除某个迭代器指向的元素
遍历
1
2
for (int x : uset)
cout << x << " ";
大小与状态
1
2
3
uset.size(); // 当前元素数量
uset.empty(); // 是否为空
uset.clear(); // 清空所有元素
哈希表相关
1
2
3
4
5
uset.bucket_count(); // 当前桶的数量
uset.load_factor(); // 当前负载因子
uset.max_load_factor(); // 最大负载因子
uset.rehash(n); // 设置桶数量
uset.reserve(n); // 预留空间,提升性能
自定义类型支持
自定义类型如结构体、类,必须提供:
- 哈希函数
- 等值比较函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Point {
int x, y;
bool operator==(const Point& other) const {
return x == other.x && y == other.y;
}
};
struct PointHash {
size_t operator()(const Point& p) const {
return hash<int>()(p.x) ^ (hash<int>()(p.y) << 1);
}
};
unordered_set<Point, PointHash> points;
也可以用 std::unordered_set<Point, PointHash, PointEqual>
显式提供相等比较函数。
性能分析与对比
操作 | std::set (红黑树) | std::unordered_set (哈希表) |
---|---|---|
插入 | O(log n) | O(1) 平均 |
删除 | O(log n) | O(1) 平均 |
查找 | O(log n) | O(1) 平均 |
有序遍历 | 支持 | 不支持(无序) |
占用内存 | 较少(树结构) | 较多(哈希表和桶) |
STL set
与 SGI hash_set
标准与实现
- STL 标准:只规范了容器的接口与复杂度要求,不规定具体实现方式。
- 常见实现:
set
多数以 红黑树(RB-tree) 为底层实现。 - SGI STL 扩展:额外提供了
hash_set
,底层使用 hashtable。
hash_set
的实现特点
hash_set
的绝大多数操作都是转调用 hashtable 的接口。- 底层机制不同:
- RB-tree:支持自动排序(
set
元素有序)。 - hashtable:不保证顺序(
hash_set
元素无序)。
- RB-tree:支持自动排序(
set
与 hash_set
的共同点
- 都可快速查找元素(复杂度:O(log n) vs O(1))。
- 元素的 键值即实值(不同于
map
的 key-value 结构)。 - 使用方式几乎相同。
限制
- 若底层 hashtable 无法处理某种类型(例如缺少
hash function
),hash_set
也无法处理,需用户自定义hash
。
源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
template <
class Value,
class HashFcn = hash<Value>, // 哈希函数,默认使用 hash<Value>
class EqualKey = equal_to<Value>, // 判断键是否相等的函数对象,默认使用 equal_to
class Alloc = alloc // 内存分配器
>
class hash_set {
private:
// identity<T>:恒等函数对象,返回参数本身。
// 在 set 中,键值即实值,所以 keyExtractor 直接用 identity
typedef hashtable<
Value, // 节点存储的类型(即 value_type)
Value, // key_type(这里与 value_type 相同)
HashFcn, // 哈希函数
identity<Value>, // 从 value 提取 key 的方式(直接返回)
EqualKey, // 键值比较方式
Alloc // 内存分配器
> ht;
ht rep; // 底层用 hashtable 实现所有功能
public:
// 一堆 typedef,直接复用 hashtable 的类型定义
typedef typename ht::key_type key_type;
typedef typename ht::value_type value_type;
typedef typename ht::hasher hasher;
typedef typename ht::key_equal key_equal;
typedef typename ht::size_type size_type;
typedef typename ht::difference_type difference_type;
typedef typename ht::const_pointer pointer;
typedef typename ht::const_pointer const_pointer;
typedef typename ht::const_reference reference;
typedef typename ht::const_reference const_reference;
typedef typename ht::const_iterator iterator; // 注意:const_iterator(set 元素不可修改)
typedef typename ht::const_iterator const_iterator;
// 获取当前使用的哈希函数对象
hasher hash_funct() const { return rep.hash_funct(); }
// 获取当前使用的键比较函数对象
key_equal key_eq() const { return rep.key_eq(); }
public:
// ===============================
// 构造函数
// ===============================
// 默认构造:初始容量 100(hashtable 会调整为 >=100 的最小质数)
hash_set() : rep(100, hasher(), key_equal()) {}
explicit hash_set(size_type n)
: rep(n, hasher(), key_equal()) {}
hash_set(size_type n, const hasher& hf)
: rep(n, hf, key_equal()) {}
hash_set(size_type n, const hasher& hf, const key_equal& eql)
: rep(n, hf, eql) {}
// 迭代器区间构造(插入时调用 insert_unique,不允许重复键)
template <class InputIterator>
hash_set(InputIterator f, InputIterator l)
: rep(100, hasher(), key_equal()) { rep.insert_unique(f, l); }
template <class InputIterator>
hash_set(InputIterator f, InputIterator l, size_type n)
: rep(n, hasher(), key_equal()) { rep.insert_unique(f, l); }
template <class InputIterator>
hash_set(InputIterator f, InputIterator l, size_type n, const hasher& hf)
: rep(n, hf, key_equal()) { rep.insert_unique(f, l); }
template <class InputIterator>
hash_set(InputIterator f, InputIterator l, size_type n,
const hasher& hf, const key_equal& eql)
: rep(n, hf, eql) { rep.insert_unique(f, l); }
public:
// ===============================
// 容量相关
// ===============================
size_type size() const { return rep.size(); }
size_type max_size() const { return rep.max_size(); }
bool empty() const { return rep.empty(); }
// 交换两个 hash_set 的内容
void swap(hash_set& hs) { rep.swap(hs.rep); }
// 友元声明(定义在类外)
friend bool operator== __STL_NULL_TMPL_ARGS
(const hash_set& hs1, const hash_set& hs2);
// ===============================
// 迭代器相关
// ===============================
iterator begin() const { return rep.begin(); }
iterator end() const { return rep.end(); }
public:
// ===============================
// 插入操作(不允许重复)
// ===============================
pair<iterator, bool> insert(const value_type& obj) {
// 调用底层 hashtable 的 insert_unique
pair<typename ht::iterator, bool> p = rep.insert_unique(obj);
return pair<iterator, bool>(p.first, p.second);
}
template <class InputIterator>
void insert(InputIterator f, InputIterator l) {
rep.insert_unique(f, l);
}
pair<iterator, bool> insert_noresize(const value_type& obj) {
// 与 insert 类似,但不会自动扩容
pair<typename ht::iterator, bool> p = rep.insert_unique_noresize(obj);
return pair<iterator, bool>(p.first, p.second);
}
// ===============================
// 查找 / 统计
// ===============================
iterator find(const key_type& key) const { return rep.find(key); }
size_type count(const key_type& key) const { return rep.count(key); }
pair<iterator, iterator> equal_range(const key_type& key) const {
return rep.equal_range(key);
}
// ===============================
// 删除
// ===============================
size_type erase(const key_type& key) { return rep.erase(key); }
void erase(iterator it) { rep.erase(it); }
void erase(iterator f, iterator l) { rep.erase(f, l); }
// 清空容器
void clear() { rep.clear(); }
public:
// ===============================
// 桶相关操作(hash table 专有)
// ===============================
void resize(size_type hint) { rep.resize(hint); }
size_type bucket_count() const { return rep.bucket_count(); }
size_type max_bucket_count() const { return rep.max_bucket_count(); }
size_type elems_in_bucket(size_type n) const { return rep.elems_in_bucket(n); }
};
// 比较运算符重载
template <class Value, class HashFcn, class EqualKey, class Alloc>
inline bool operator==(
const hash_set<Value, HashFcn, EqualKey, Alloc>& hs1,
const hash_set<Value, HashFcn, EqualKey, Alloc>& hs2) {
return hs1.rep == hs2.rep;
}
本文由作者按照 CC BY 4.0 进行授权