【C++】:STL详解 —— unordered_set 和 unordered_map类

目录

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

unordered_set的概念

unordered_set 是 C++ 标准库中的一个容器,用于存储唯一元素,基于哈希表实现

1. 基本特性

  • 唯一性:元素唯一,不允许重复。

  • 无序性:元素不按特定顺序存储,遍历顺序不确定。

  • 基于哈希表:通过哈希函数将元素映射到桶(bucket)中,提供平均 O(1) 的查找、插入和删除操作(最坏情况 O(n))。

2. 实现原理

  • 哈希函数:每个元素通过哈希函数计算哈希值,决定其存储的桶位置。

  • 冲突处理:通常采用链地址法(每个桶内用链表管理冲突元素)。

  • 动态扩容:当负载因子(元素数 / 桶数)超过阈值时,自动扩容并重新哈希。

3. 时间复杂度

  • 平均情况:插入、删除、查找操作均为 O(1)。

  • 最坏情况:所有元素哈希到同一桶中,导致操作退化为 O(n)。

unordered_set的构造函数

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的使用

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

unordered_map的概念

unordered_map 是一个关联容器,用于存储 键值对(Key-Value Pairs),基于 哈希表(Hash Table) 实现。

  • 键(Key)唯一性:每个键最多出现一次,值(Value)可以重复。

  • 无序性:元素不按键的顺序存储,遍历顺序由哈希函数和冲突策略决定。

  • 高效查找:平均时间复杂度为 O(1)(最坏情况 O(n))。

核心特性

特性 说明
底层结构 哈希表(数组 + 链表/红黑树处理冲突)
键唯一性 每个键只能出现一次,重复插入会覆盖原有值
动态扩容 当负载因子(元素数/桶数)超过阈值时,自动扩容并重新哈希
哈希函数依赖 键的类型必须支持哈希函数(std::hash 或自定义)
键比较方式 通过 operator== 或自定义比较器判断键是否相等

注意:键的类型要求

  • 必须支持哈希函数
    内置类型(如 intstring)可直接使用,自定义类型需定义哈希函数。

  • 必须支持相等比较
    通过 operator== 或自定义比较器判断键是否相等。

unordered_map的构造函数

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的使用

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);

 注意事项

  1. 键不可变性:键的内容插入后不可修改(否则哈希值变化,导致未定义行为)。

  2. 迭代器失效:插入可能触发扩容,导致所有迭代器失效;删除仅影响被删元素的迭代器。

  3. 哈希冲突:设计高效哈希函数以减少冲突(如结合多个字段的哈希值)。

  4. operator[] 的副作用:若键不存在,map[key] 会插入默认值,可能不符合预期。

unordered_multiset

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_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
适用场景 键唯一的快速映射 允许重复键的多值映射

unordered_set、unordered_map 和 set、map的区别

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)
接口特性 支持范围查询、有序遍历 支持桶管理、哈希策略控制

你可能感兴趣的:(重制C++版,c++,开发语言,c语言,数据结构,算法,哈希算法,散列表)