在C++标准库(STL)中,unordered_set
是一个无序集合,它底层采用哈希表实现,提供快速的查找、插入和删除操作。与 set
不同,unordered_set
不会自动排序元素,而是依据哈希函数存储元素,因此其操作的时间复杂度通常为 O(1)。
1. unordered_set 的基本特点
-
底层实现:基于哈希表(通常是哈希桶 + 链表或开放地址法)。
-
元素唯一性:不允许存储重复元素。
-
无序存储:元素的存储顺序不固定,不支持索引访问。
-
时间复杂度:平均情况下,查找、插入、删除操作的时间复杂度为 O(1),最坏情况下可能退化为 O(n)。
-
哈希函数:默认使用
std::hash<T>
,但可以自定义哈希函数。后面详细有讲。 -
默认情况下,
unordered_set
没有为pair<int, int>
提供默认的哈希函数和比较函数。需要自定义哈希函数。这个哈希函数可以适用于任意类型的pair
。这个哈希函数可以适用于任意类型的pair
,只要T1
和T2
有对应的std::hash
特化// 自定义哈希函数 struct pair_hash { template <class T1, class T2> size_t operator() (const pair<T1, T2>& p) const { auto hash1 = hash<T1>{}(p.first); // 哈希第一个元素 auto hash2 = hash<T2>{}(p.second); // 哈希第二个元素 return hash1 ^ (hash2 << 1); // 组合哈希值 } }; //后续 unordered_set<pair<int, int>, pari_hash> T;
pair_hash
结构体是一个自定义的哈希函数,用于对pair
类型的对象进行哈希。这个哈希函数的目的是将pair
的两个元素(first
和second
)组合成一个哈希值,以便可以将pair
用作unordered_set
或unordered_map
的键。
模板参数: T1 和 T2 是 pair 的两个元素的类型。 这个哈希函数可以适用于任意类型的 pair。 哈希计算: hash<T1>{}(p.first):使用 std::hash 对 pair 的第一个元素 p.first 进行哈希。 hash<T2>{}(p.second):使用 std::hash 对 pair 的第二个元素 p.second 进行哈希。 组合哈希值: hash1 ^ (hash2 << 1):将两个哈希值组合在一起。 hash2 << 1:将 hash2 左移一位,目的是避免 hash1 和 hash2 直接异或时可能导致的冲突。 ^:异或操作,将两个哈希值混合在一起。
#include <iostream> #include <unordered_set> #include <utility> // for std::pair using namespace std; struct pair_hash { template <class T1, class T2> size_t operator() (const pair<T1, T2>& p) const { auto hash1 = hash<T1>{}(p.first); auto hash2 = hash<T2>{}(p.second); return hash1 ^ (hash2 << 1); } }; int main() { unordered_set<pair<int, int>, pair_hash> us; us.insert({1, 2}); us.insert({3, 4}); us.insert({1, 2}); // 不会插入,因为 {1, 2} 已经存在 for (const auto& p : us) { cout << "(" << p.first << ", " << p.second << ")\n"; } return 0; }
2. unordered_set 的常见操作
2.1 头文件与基本定义
定义和初始化
unordered_set<int> us1; // 定义一个空的unordered_set,存储int类型的元素 unordered_set<int> us2 = {1, 2, 3, 4, 5}; // 使用初始化列表初始化unordered_set unordered_set<int> us3(us2); // 使用另一个unordered_set初始化
#include <iostream>
#include <unordered_set>
using namespace std;
int main() {
unordered_set<int> uset = {10, 20, 30, 40};
// 遍历 unordered_set
for (int val : uset) {
cout << val << " ";
}
return 0;
}
2.2 插入元素
可以使用
insert
方法向unordered_set
中插入元素。由于unordered_set
中的元素是唯一的,如果插入的元素已经存在,则插入操作不会生效。
unordered_set<int> uset;
uset.insert(10);
uset.insert(20);
uset.insert(10); // 这个插入操作不会生效,因为10已经存在
2.3 查找元素
可以使用
find
方法查找unordered_set
中的元素。如果元素存在,find
会返回指向该元素的迭代器;如果元素不存在,则返回end()
迭代器.
if (uset.find(20) != uset.end()) {
cout << "Found 20";
} else {
cout << "Not Found";
}
2.4 删除元素
可以使用
erase
方法删除unordered_set
中的元素。erase
方法可以接受一个值或一个迭代器作为参数。
unordered_set<int> us = {1, 2, 3, 4, 5};
us.erase(3); // 删除值为3的元素
us.erase(us.begin()); // 删除第一个元素
2.5 遍历 unordered_set
可以使用迭代器 it 遍历
unordered_set
中的所有元素。由于unordered_set
是无序的,遍历时元素的顺序是不确定的。
unordered_set<int> us = {5, 3, 1, 4, 2};
for (auto it = us.begin(); it != us.end(); ++it) {
cout << *it << " ";
}
// 输出顺序可能为: 1 2 3 4 5 或其他顺序
2.6其他常用操作
-
size():返回
unordered_set
中元素的数量。 -
empty():判断
unordered_set
是否为空。 -
clear():清空
unordered_set
中的所有元素。
unordered_set<int> us = {1, 2, 3};
cout << "Size: " << us.size() << endl; // 输出: 3
us.clear();
cout << "Is empty: " << us.empty() << endl; // 输出: 1 (true)
2.7 唯一性
unordered_set
中的元素是唯一的,插入重复的元素不会生效。如果需要存储重复的元素,可以使用unordered_multiset
。
unordered_multiset<int> ums = {1, 2, 2, 3, 3, 3};
for (auto it = ums.begin(); it != ums.end(); ++it) {
cout << *it << " ";
}
// 输出: 1 2 2 3 3 3
2.8 无序性
unordered_set
中的元素存储顺序是无序的,不会按照任何特定的顺序排列。如果需要有序的集合,可以使用set
。
set<int> s = {5, 3, 1, 4, 2};
for (auto it = s.begin(); it != s.end(); ++it) {
cout << *it << " ";
}
// 输出: 1 2 3 4 5
3. 自定义哈希函数
如果 unordered_set
存储自定义类型,需要提供哈希函数和相等性比较函数。例如:
struct Point {
int x, y;
bool operator==(const Point &p) const {
return x == p.x && y == p.y;
}
};
struct HashPoint {
size_t operator()(const Point &p) const {
return hash<int>()(p.x) ^ (hash<int>()(p.y) << 1);
}
};
unordered_set<Point, HashPoint> pointSet;
示例:可以定义一个结构体,并重载operator()
来指定哈希函数。
struct MyHash {
size_t operator()(int key) const {
return key % 10; // 简单的哈希函数
}
};
unordered_set<int, MyHash> us = {1, 2, 3, 4, 5};
for (auto it = us.begin(); it != us.end(); ++it) {
cout << *it << " ";
}
// 输出顺序可能为: 1 2 3 4 5 或其他顺序
4. 应用场景
-
去重:快速去除重复元素。
-
高效查找:O(1) 复杂度的元素查找。
-
集合操作:可以用于实现集合的并、交、差运算。
5. 总结
unordered_set
是 STL 中高效的无序集合容器,适用于需要高效查找的场景。
由于unordered_set
底层使用哈希表实现,因此它的插入、删除和查找操作的平均时间复杂度都是O(1)。这使得unordered_set
在处理需要频繁查找和插入的场景时非常高效。然而,unordered_set
的内存开销相对较大,因为它需要维护哈希表的结构。
它的时间复杂度通常为 O(1),但由于哈希冲突可能退化到 O(n),因此选择合适的哈希函数至关重要。
在实际开发中,unordered_set
可以用于需要快速查找和去重的场景,例如统计单词出现的次数、维护唯一的元素集合等。