目录
unordered_set
unordered_set的概念
unordered_set的构造函数
unordered_set的使用
unordered_map
unordered_map的概念
unordered_map的构造函数
unordered_map的使用
unordered_multiset
unordered_multimap
unordered_set、unordered_map 和 set、map的区别
unordered_set
是 C++ 标准库中的一个容器,用于存储唯一元素,基于哈希表实现
1. 基本特性
唯一性:元素唯一,不允许重复。
无序性:元素不按特定顺序存储,遍历顺序不确定。
基于哈希表:通过哈希函数将元素映射到桶(bucket)中,提供平均 O(1) 的查找、插入和删除操作(最坏情况 O(n))。
2. 实现原理
哈希函数:每个元素通过哈希函数计算哈希值,决定其存储的桶位置。
冲突处理:通常采用链地址法(每个桶内用链表管理冲突元素)。
动态扩容:当负载因子(元素数 / 桶数)超过阈值时,自动扩容并重新哈希。
3. 时间复杂度
平均情况:插入、删除、查找操作均为 O(1)。
最坏情况:所有元素哈希到同一桶中,导致操作退化为 O(n)。
1. 默认构造函数
创建一个空的 unordered_set
,桶数量和哈希函数/比较函数使用默认值。
unordered_set s1; // 空的 unordered_set
2. 指定桶数量的构造函数
创建一个空的 unordered_set
,并预分配桶数量(避免扩容开销)。
unordered_set s2(100); // 初始桶数量为 100
3. 范围构造函数
通过迭代器范围(如数组、vector
、其他容器)构造 unordered_set
。
vector vec = {1, 2, 3, 2, 4};
unordered_set s3(vec.begin(), vec.end()); // 包含 {1, 2, 3, 4}
4. 拷贝构造函数
通过另一个 unordered_set
拷贝构造新容器。
unordered_set s4(s3); // s4 是 s3 的拷贝
5. 移动构造函数
通过另一个 unordered_set
移动构造新容器(高效转移资源)。
unordered_set s5(std::move(s4)); // s4 变为空,资源转移给 s5
6. 初始化列表构造函数
直接通过初始化列表构造 unordered_set
。
unordered_set s6 = {1, 2, 3, 4}; // 直接初始化
7. 自定义哈希函数和比较函数的构造函数
当元素类型需要自定义哈希或比较逻辑时,需在构造函数中显式指定:
struct Person
{
int id;
std::string name;
bool operator==(const Person& other) const
{
return id == other.id;
}
};
// 自定义哈希函数
struct PersonHash
{
size_t operator()(const Person& p) const
{
return std::hash()(p.id);
}
};
// 自定义比较函数
struct PersonEqual
{
bool operator()(const Person& a, const Person& b) const
{
return a.id == b.id;
}
};
int main()
{
// 显式指定哈希函数和比较函数
std::unordered_set s7;
s7.insert({42, "Alice"});
return 0;
}
8. 指定负载因子的构造函数
可同时指定初始桶数量和最大负载因子(默认负载因子为 1.0
)。
std::unordered_set s8(100, /* 初始桶数量 */
std::hash(), /* 哈希函数 */
std::equal_to(), /* 比较函数 */
0.75f /* 最大负载因子 */);
unordered_set当中常用的成员函数如下:
接口名称 | 作用 |
---|---|
insert |
插入元素 |
emplace |
原地构造元素 |
erase |
删除元素 |
clear |
清空集合 |
find |
查找元素并返回迭代器 |
count |
检查元素是否存在 |
contains (C++20+) |
直接判断元素是否存在 |
size |
返回元素数量 |
empty |
判断集合是否为空 |
哈希的接口
bucket_count |
返回当前桶的数量 |
load_factor |
返回当前负载因子 |
max_load_factor |
设置/获取最大负载因子 |
begin /end |
返回遍历集合的迭代器 |
bucket |
返回元素所在的桶索引 |
bucket_size |
返回指定桶中的元素数量 |
reserve |
预分配空间以减少扩容次数 |
rehash |
强制调整桶数量 |
代码示例:
unordered_set s = { 100,66,33,22020,3 };
// 插入元素(去重)
s.insert(1);
s.insert(4);
s.emplace(2);
s.emplace(9);
// 遍历容器(范围for)
for (auto e : s)
{
cout << e << " ";
}
cout << endl; // 100 66 33 22020 3 1 4 2 9
//删除元素
s.erase(3);
unordered_set::iterator pos = s.find(1); //查找值为1的元素
if (pos != s.end())
{
s.erase(pos);
}
//遍历容器(迭代器遍历)
unordered_set::iterator it = s.begin();
while (it != s.end())
{
cout << *it << " ";
it++;
}
cout << endl; // 100 66 33 22020 4 2 9
//容器中值为2的元素个数
cout << s.count(2) << endl; // 1
//容器大小
cout << s.size() << endl; // 7
//清空容器
s.clear();
//容器判空
cout << s.empty() << endl; // 1
//交换两个容器的数据
unordered_set tmp = { 11, 22, 33, 44 };
s.swap(tmp);
for (auto e : s)
{
cout << e << " ";
}
cout << endl; //11 22 33 44
unordered_map
是一个关联容器,用于存储 键值对(Key-Value Pairs),基于 哈希表(Hash Table) 实现。
键(Key)唯一性:每个键最多出现一次,值(Value)可以重复。
无序性:元素不按键的顺序存储,遍历顺序由哈希函数和冲突策略决定。
高效查找:平均时间复杂度为 O(1)(最坏情况 O(n))。
核心特性
特性 | 说明 |
---|---|
底层结构 | 哈希表(数组 + 链表/红黑树处理冲突) |
键唯一性 | 每个键只能出现一次,重复插入会覆盖原有值 |
动态扩容 | 当负载因子(元素数/桶数)超过阈值时,自动扩容并重新哈希 |
哈希函数依赖 | 键的类型必须支持哈希函数(std::hash 或自定义) |
键比较方式 | 通过 operator== 或自定义比较器判断键是否相等 |
注意:键的类型要求
必须支持哈希函数:
内置类型(如 int
, string
)可直接使用,自定义类型需定义哈希函数。
必须支持相等比较:
通过 operator==
或自定义比较器判断键是否相等。
1. 默认构造函数
创建一个空的 unordered_map
,桶数量和哈希函数/比较函数使用默认值。
unordered_map m1;
2. 指定桶数量的构造函数
创建一个空的 unordered_map
,并预分配桶数量(避免扩容开销)。
unordered_map m2(100); // 初始桶数量为 100
3. 范围构造函数
通过迭代器范围(如数组、vector
、其他容器)构造 unordered_map
。
vector > vec = { {"abc", 1}, {"xo", 2}, {"wh", 3} };
unordered_map m3(vec.begin(), vec.end());
4. 拷贝构造函数
通过另一个 unordered_map
拷贝构造新容器。
unordered_map m4(m3); // m4 是 m3 的拷贝
5. 移动构造函数
通过另一个 unordered_map
移动构造新容器(高效转移资源)。
unordered_map m5(std::move(m4)); // m4 变为空,资源转移给 m5
6. 初始化列表构造函数
直接通过初始化列表构造 unordered_map
。
unordered_map m6 = { {"abc", 1}, {"xo", 2}, {"wh", 3} }; // 直接初始化
7. 自定义哈希函数和比较函数的构造函数
struct Person
{
std::string name;
int age;
bool operator==(const Person& other) const
{
return name == other.name && age == other.age;
}
};
// 自定义哈希函数
struct PersonHash
{
size_t operator()(const Person& p) const
{
return std::hash()(p.name) ^ std::hash()(p.age);
}
};
// 构造时指定哈希函数和比较器
std::unordered_map map7;
map7[{ "Alice", 30 }] = "Engineer";
unordered_map当中常用的成员函数如下:
操作类型 | 接口方法 | 作用 | 示例 |
---|---|---|---|
插入 | insert(pair) |
插入键值对 | map.insert({"key", 100}); |
emplace(args...) |
原地构造键值对 | map.emplace("key", 100); |
|
operator[] |
插入或修改值 | map["key"] = 100; |
|
删除 | erase(key) |
按键删除 | map.erase("key"); |
erase(iterator) |
按迭代器删除 | map.erase(it); |
|
查找 | find(key) |
返回迭代器或 end() |
auto it = map.find("key"); |
count(key) |
返回是否存在(0 或 1) | if (map.count("key")) { ... } |
|
contains(key) (C++20) |
直接返回布尔值 | if (map.contains("key")) { ... } |
|
容量 | size() |
返回元素数量 | int n = map.size(); |
empty() |
判断是否为空 | if (map.empty()) { ... } |
|
遍历 | begin() /end() |
迭代器遍历 | for (auto it = map.begin(); ... ) |
性能优化 | reserve(n) |
预分配空间 | map.reserve(1000); |
rehash(n) |
调整桶数量 | map.rehash(512); |
注意事项
键不可变性:键的内容插入后不可修改(否则哈希值变化,导致未定义行为)。
迭代器失效:插入可能触发扩容,导致所有迭代器失效;删除仅影响被删元素的迭代器。
哈希冲突:设计高效哈希函数以减少冲突(如结合多个字段的哈希值)。
operator[]
的副作用:若键不存在,map[key]
会插入默认值,可能不符合预期。
unordered_multiset
和 unordered_set
是 C++ STL 中基于哈希表实现的容器,主要用于高效存储和查找元素。它们的核心区别在于 是否允许重复元素,同时在一些操作行为和性能上存在差异。以下是两者的详细对比:
count(key)
方法
unordered_set
:返回值只能是 0
或 1
(元素存在与否)。
unordered_multiset
:返回与 key
相等的元素数量(可能大于 1)。
find(key)
方法
两者都返回第一个匹配元素的迭代器。
对于 unordered_multiset
,若存在多个相同元素,可以通过迭代器遍历后续元素。
总结
特性 | unordered_set |
unordered_multiset |
---|---|---|
元素唯一性 | 唯一 | 允许重复 |
insert 返回值 |
pair |
迭代器 |
count(key) |
0 或 1 | ≥0 |
适用场景 | 去重、唯一标识符 | 允许重复的快速查找与统计 |
unordered_multimap
和 unordered_map
是 C++ STL 中基于哈希表实现的关联容器,主要用于高效存储键值对(key-value pairs)。它们的核心区别在于 是否允许键(key)重复,同时在插入、查找等操作的行为和接口上存在差异。以下是两者的详细对比:
count(key)
方法
unordered_map
:返回值只能是 0
或 1
(键存在与否)。
unordered_multimap
:返回与 key
关联的键值对数量(可能大于 1)。
find(key)
方法
两者均返回第一个匹配键的迭代器。
对于 unordered_multimap
,若存在多个相同键的键值对,可通过迭代器遍历后续元素,或使用 equal_range(key)
获取所有匹配的键值对范围。
总结
特性 | unordered_map |
unordered_multimap |
---|---|---|
键唯一性 | 唯一 | 允许重复 |
insert 返回值 |
pair |
迭代器 |
operator[] 和 at() |
支持 | 不支持 |
count(key) |
0 或 1 | ≥0 |
适用场景 | 键唯一的快速映射 | 允许重复键的多值映射 |
1. 底层实现
容器 | 底层结构 | 元素顺序 | 依赖条件 |
---|---|---|---|
set /map |
红黑树(平衡二叉搜索树) | 元素按键有序排列(默认升序) | 需定义键的比较函数(如 < 或自定义比较器) |
unordered_set /unordered_map |
哈希表 | 元素无序存储 | 需定义键的哈希函数和相等比较函数 |
2. 时间复杂度对比
操作 | set /map |
unordered_set /unordered_map |
---|---|---|
插入 | O(log n) | 平均 O(1),最坏 O(n)(哈希冲突严重时) |
删除 | O(log n) | 平均 O(1),最坏 O(n) |
查找 | O(log n) | 平均 O(1),最坏 O(n) |
遍历有序元素 | O(n)(天然有序) | O(n)(顺序不可控) |
哈希表性能依赖哈希函数质量:若哈希函数分布不均,冲突频繁,性能会显著下降。
红黑树性能稳定:无论数据分布如何,操作时间均为对数复杂度。
3. 内存占用
set
/map
红黑树需要额外存储节点颜色、父子指针等元数据,但整体结构紧凑。
unordered_set
/unordered_map
哈希表需要维护桶(buckets)和链表(处理冲突),可能存在内存碎片和未使用的桶空间,内存占用通常更高。
4. 迭代器稳定性
set
/map
插入/删除操作不会使迭代器失效(除非删除当前元素)。
unordered_set
/unordered_map
插入操作可能导致哈希表扩容(rehash),使所有迭代器失效;删除操作仅影响被删元素的迭代器。
5. 适用场景
容器 | 适用场景 |
---|---|
set /map |
需要元素/键有序、范围查询、稳定性能(如数据库索引、字典序处理) |
unordered_set /unordered_map |
需要快速访问、不关心顺序、数据量大且哈希函数高效(如缓存、去重计数器) |
代码示例:
// set(有序)
std::set ordered_set;
ordered_set.insert(3);
ordered_set.insert(1);
ordered_set.insert(2);
std::cout << "Set elements (ordered): ";
for (int x : ordered_set) std::cout << x << " "; // 输出 1 2 3
// unordered_set(无序)
std::unordered_set unordered_set;
unordered_set.insert(3);
unordered_set.insert(1);
unordered_set.insert(2);
std::cout << "\nUnordered_set elements: ";
for (int x : unordered_set) std::cout << x << " "; // 输出顺序不确定(如 3 1 2)
总结表格
特性 | set /map |
unordered_set /unordered_map |
---|---|---|
底层结构 | 红黑树 | 哈希表 |
元素顺序 | 有序 | 无序 |
插入/查找时间复杂度 | O(log n) | 平均 O(1),最坏 O(n) |
内存占用 | 较低 | 较高(桶和链表开销) |
迭代器稳定性 | 稳定(除非删除当前元素) | 插入可能导致全部失效(rehash) |
接口特性 | 支持范围查询、有序遍历 | 支持桶管理、哈希策略控制 |