目录

unordered_map

unordered_map 概述

unordered_map 是 C++ STL 中的关联容器,基于**哈希表(哈希桶)**实现,用于存储键值对(key-value pairs)。其核心特点包括:

  • 无序性不维护键的存储顺序 ,与 map(基于红黑树)的有序性形成对比。
  • 高效性 :平均情况下,插入、查询、删除操作的时间复杂度为 O(1) ,但最坏情况下可能退化为 O(n)(如哈希冲突严重时)。
  • 键唯一性:每个键(key)唯一,重复插入会覆盖原有值。

核心操作复杂度分析

操作 平均时间复杂度 最坏时间复杂度 场景说明
查询 O(1) O(n) 哈希冲突导致链表遍历
插入 O(1) O(n) 哈希表扩容(rehash)时触发
删除 O(1) O(n) 同查询复杂度
修改 O(1) O(n) 修改需先查询键,再更新值

关键说明

  1. 哈希冲突 :不同键映射到同一哈希桶时,需遍历链表查找目标键值对,极端情况下所有键冲突,时间复杂度退化为 O(n) 。
  2. 哈希表扩容 :当元素数量超过 负载因子(load factor) * 桶数量 时,哈希表自动扩容并重新哈希(rehash),此操作耗时 O(n),但均摊到每次插入仍为 O(1) 。

底层实现原理

unordered_map 的底层结构由哈希函数哈希桶组成,具体实现如下:

1. 哈希表结构
  • 哈希桶(Buckets):一个动态数组,每个元素指向一个链表(或单向链表头节点)。
  • 哈希函数 :将键转换为哈希值,决定键值对存储的桶位置。例如,std::hash<Key> 是默认哈希函数 。
2. 冲突解决
  • 链地址法(Separate Chaining) : 当多个键映射到同一桶时,键值对以链表形式存储在该桶中。插入新元素时,通常将新节点添加到链表头部(O(1) 操作) 。
3. 动态扩容机制
  • 负载因子 :定义为 元素数量 / 桶数量,默认阈值为 1.0。超过阈值时,桶数量翻倍并重新哈希所有元素 。
4. 迭代器失效
  • 插入/删除:仅影响当前操作的桶和链表中的迭代器。
  • 扩容(rehash) :所有迭代器失效。

性能优化建议

  1. 选择高效哈希函数
    • 均匀分布键的哈希值,减少冲突(如自定义哈希函数处理字符串或复杂对象)。
  2. 预分配桶数量
    • 若已知元素规模,初始化时指定桶数量(如 unordered_map<int, int>map(1000);),减少扩容次数 。
  3. 调整负载因子
    • 通过 max_load_factor() 修改阈值,平衡内存占用与冲突概率 。
cpp 复制代码
#include <unordered_map>  
#include <iostream>  

int main() {  
    std::unordered_map<std::string, int> scores = {{"Alice", 90}, {"Bob", 85}};  

    // 插入  
    scores["Charlie"] = 88;  
    scores.insert({"Dave", 92});  

    // 查询  
    if (scores.find("Alice") != scores.end()) {  
        std::cout << "Alice's score: " << scores["Alice"] << std::endl;  // 输出 90  
    }  

    // 删除  
    scores.erase("Bob");  

    // 修改  
    scores["Charlie"] = 95;  

    // 遍历(无序)  
    for (const auto& pair : scores) {  
        std::cout << pair.first << ": " << pair.second << std::endl;  
    }  
    return 0;  
}  
cpp 复制代码
#include <iostream>
#include <unordered_map>
#include <string>

// 自定义哈希函数:所有字符串的哈希值强制为 0(仅用于演示冲突)
struct ConflictHash {
    size_t operator()(const std::string& key) const noexcept {
        return 0; // 所有键的哈希值相同,确保冲突
    }
};

int main() {
    // 使用自定义哈希函数,初始桶数量设为 3(便于观察)
    std::unordered_map<std::string, int, ConflictHash> scores;
    scores.max_load_factor(1.0); // 关闭自动扩容,固定桶数量

    std::cout << "初始桶数量: " << scores.bucket_count() << "\n\n";

    // 插入多个键值对(哈希值均为 0,全部冲突)
    scores["Alice"] = 90;
    scores["Bob"] = 85;
    scores["Charlie"] = 88;
    scores["Dave"] = 92;

    // 打印哈希表状态
    std::cout << "插入后桶数量: " << scores.bucket_count() << std::endl;
    std::cout << "负载因子: " << scores.load_factor() << "\n\n";

    // 遍历所有桶,观察冲突情况
    for (size_t i = 0; i < scores.bucket_count(); ++i) {
        std::cout << "桶 " << i << " 的元素数量: " << scores.bucket_size(i) << std::endl;
    }

    // 查询操作(需遍历链表)
    std::cout << "\n查询 \"Dave\" 的分数: ";
    auto it = scores.find("Dave");
    if (it != scores.end()) {
        std::cout << it->second << std::endl; // 输出 92
    }

    // 删除操作(同样需遍历链表)
    scores.erase("Bob");
    std::cout << "\n删除 \"Bob\" 后,桶 0 的元素数量: " << scores.bucket_size(0) << std::endl;

    return 0;
}

哈希桶的存储内容

  1. 键值对集合

    • 每个哈希桶存储的是 发生哈希冲突的键值对 。例如,在 C++ 的 unordered_map 中,每个桶实际上是一个链表头节点,链表中每个节点包含:

      • 键(Key):用于唯一标识元素的键。
      • 值(Value):与键关联的数据。
      • 指向下一个节点的指针:解决冲突时将新元素链入链表。
cpp 复制代码
template<class T>
struct HashNode {
    T _data;         // 存储键值对(如 pair<Key, Value>)
    HashNode<T>* _next; // 指向冲突的下一个节点
};
  1. 动态链表结构

    • 当不同键通过哈希函数映射到同一桶时,新元素会以头插法或尾插法添加到链表中。例如,插入键 "Alice""Bob" 若冲突,则它们在同一个链表中顺序存储。

哈希桶的作用与特点

  1. 冲突管理

    • 哈希桶通过链表(或红黑树)管理冲突元素,确保哈希表在冲突时仍能正常操作(如插入、查询、删除)。
  2. 性能影响

    • 理想情况 :若哈希函数均匀,每个桶内链表长度为 1,操作时间复杂度为 O(1)
    • 最坏情况 :所有键冲突,哈希表退化为链表,操作时间复杂度为 O(n)
  3. 内存结构

    • 哈希桶的底层通常由动态数组(如 std::vector)实现,数组的每个位置指向一个链表头节点 。

哈希桶的示例场景

unordered_map<string, int> 为例:

  1. 插入键值对 ("Alice", 90)

    • 哈希函数计算 "Alice" 的哈希值,假设映射到桶 3。
    • 桶 3 的链表存入该键值对。
  2. 插入键值对 ("Bob", 85) (假设与 "Alice" 冲突):

    • 哈希值同样映射到桶 3,新键值对添加到桶 3 的链表头部或尾部。
  3. 查询 "Bob"

    • 哈希函数定位到桶 3,遍历链表查找键 "Bob" 对应的节点。

总结

哈希桶中存储的是 哈希冲突时映射到同一位置的键值对集合,通过链表管理冲突元素。这种设计平衡了空间效率与时间效率,是哈希表实现快速操作(平均 O(1))的核心机制。实际开发中,需选择高质量的哈希函数以减少冲突。

本文是转载文章,点击查看原文
如有侵权,请联系 xyy@jishuzhan.net 删除
相关推荐
今晚打老虎8 分钟前
c++第三课(基础c)
c语言·c++·算法
武帝为此11 分钟前
【C语言中联合体(共用体)详解】
c语言·算法
小百小摆1 小时前
Acwing6118 蛋糕游戏
数据结构·c++·算法
pipip.1 小时前
BFS解决----多源最短路径问题
算法·宽度优先
Tryagein3 小时前
【数据结构】并查集
数据结构·算法
Joe_Wang53 小时前
[数据结构]并查集(系统整理版)
数据结构·c++·算法·leetcode·并查集
进击的jerk5 小时前
力扣.旋转矩阵Ⅱ
算法·leetcode·矩阵
Jcqsunny6 小时前
[分层图] 汽车加油行驶问题
算法·dp·分层图·分层图dp
熬夜造bug6 小时前
LeetCode Hot100 刷题笔记(4)—— 二叉树、图论
笔记·算法·leetcode
小陈的进阶之路6 小时前
数据结构(并查集,图)
数据结构·c++·算法