跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

C++ 哈希表原理、冲突解决及性能分析

哈希表的基本原理、哈希函数设计及冲突解决方法(链表法、开放寻址法)。通过 C++ 标准库中的 unordered_set 和 unordered_map 展示了实际用法,并对比了哈希表与红黑树在不同数据场景下的性能。结论表明,哈希表在随机数据下查找插入更快,而红黑树在有序数据及稳定性要求高时更具优势。

深海蔚蓝发布于 2026/3/22更新于 2026/6/2640 浏览
C++ 哈希表原理、冲突解决及性能分析

一、哈希表概述

1. 什么是哈希表

文章配图

哈希表是一种基于数组的数据结构,用于快速地存储和查找数据。它通过一个哈希函数将元素的键值映射到哈希表中的一个位置,从而实现常数时间复杂度 O(1) 的查找和插入操作。

哈希表的基本工作原理如下:

  • 键值对存储:每个元素都有一个'键'与其对应的'值'。
  • 哈希函数:根据'键'计算出该元素的存储位置(哈希值)。
  • 数组存储:将哈希值作为索引,快速存取对应的值。

文章配图

2. 哈希表的优点

  • 高效查找:对于随机访问、插入和删除操作,哈希表能提供接近 O(1) 的时间复杂度,这是因为它能直接通过哈希函数定位到元素的存储位置。
  • 空间利用:哈希表通常比其他结构(如树)更节省空间,尤其是在大规模数据的存储上表现优秀。

3. 哈希表的缺点

  • 内存浪费:为了避免冲突,哈希表通常需要较大的内存空间。
  • 哈希冲突:当不同的键经过哈希函数计算后,映射到同一位置时,会发生哈希冲突,需要通过一定方法来解决。

二、哈希函数

哈希函数是哈希表的核心,它负责将输入的键值映射到一个固定范围内的哈希值。一个好的哈希函数应当具备以下几个特点:

  • 均匀性:哈希值应均匀分布,避免集中在哈希表的某些区域。
  • 效率:哈希函数应当简单、计算速度快。
  • 低碰撞率:哈希函数应尽量减少哈希冲突的发生。

常见哈希函数

哈希函数的发展已经有很多年历史了,在前辈的实践之下,留下了这些常见的哈希函数。

1. 直接定址法(常用)

函数原型:HashI = A * key + B

优点:简单、均匀 缺点:需要提前知道键值的分布情况 适用场景:范围比较集中,每个数据分配一个唯一位置

2. 除留余数法(常用)

文章配图

假设哈希表的大小为 m

函数原型:HashI = key % p (p < m)

优点:简单易用,性能均衡 缺点:容易出现哈希冲突,需要借助特定方法解决 适用场景:范围不集中,分布分散的数据

3. 平方取中法(了解)

函数原型:HashI = mid(key * key)

适用场景:不知道键值的分布,而位数又不是很大的情况 假设键值为 1234,对其进行平方后得到 1522756,取其中间的三位数 227 作为哈希值

4. 折叠法(了解)

折叠法是将键值从左到右分割成位数相等的几部分 (最后一部分位数可以短些),然后将这几部分叠加求和,并按哈希表表长,取后几位作为散列地址

适用场景:事先不需要知道键值的分布,且键值位数比较多 假设键值为 85673113,分为三部分 856、731、13,求和:1600,根据表长(假设为 100),哈希值就是 600

5. 随机数法(了解)

选择一个随机函数,取键值的随机函数值为它的哈希值

函数原型:HashI = rand(key) 其中 rand 为随机数函数

适用场景:键值长度不等时

哈希函数还有很多很多种,最终目的都是为了计算出重复度低的哈希值。

最常用的是直接定址法和除留余数法。

三、哈希冲突的原因和解决方法

哈希表是一种高效的数据结构,广泛应用于各种算法中,如查找、插入和删除操作等。然而,哈希表并非没有缺点,最常见的问题之一就是哈希冲突。哈希冲突发生在多个元素被映射到哈希表中的同一个位置时。理解哈希冲突的原因以及解决方法对于优化哈希表的性能至关重要。

一、哈希冲突的原因

哈希冲突的产生主要由以下几个原因导致:

文章配图

文章配图

1. 哈希函数的设计问题

哈希函数的主要任务是将输入数据(如字符串或整数)映射到哈希表的索引上。理想的哈希函数应该能够均匀地分布所有可能的输入值。然而,在实际应用中,哈希函数可能会出现设计不当的情况,例如:

  • 哈希函数映射范围过小,导致许多不同的输入值映射到同一个索引。
  • 哈希函数的输出分布不均匀,导致哈希表中某些区域频繁发生冲突。
2. 输入数据的特点

哈希冲突的发生也可能与输入数据本身的特性有关。例如,如果多个数据项本身非常相似(如有相同的前缀或后缀),即使哈希函数设计得当,它们也可能被映射到相同的哈希桶。

3. 哈希表的大小和负载因子

哈希表的大小决定了它能容纳多少元素。当哈希表的负载因子(即哈希表中元素的数量与表的容量之比)过高时,哈希冲突的概率也会增加。即使哈希函数设计得很好,如果哈希表容量不足以存储所有元素,哈希冲突不可避免。

二、哈希冲突的解决方法

为了解决哈希冲突,通常有两种主要的方法:开放寻址法和链表法。每种方法都有其优缺点,适用于不同的场景。

1. 链表法(Separate Chaining)

链表法是最常见的哈希冲突解决方案。其基本思路是,在哈希表的每个位置上,存储一个链表或其他数据结构,用来存储发生冲突的多个元素。

文章配图

具体步骤:

  • 每当发生冲突时,将新的元素插入到该位置的链表中。
  • 查找时,首先通过哈希函数找到对应的桶,再遍历桶中的链表进行查找。

优点:

  • 解决哈希冲突的简单性:链表法能够很自然地处理哈希冲突。
  • 动态扩展性:如果负载因子过高,链表的长度会增加,但不至于导致性能大幅下降。

缺点:

  • 空间浪费:每个桶都需要存储一个额外的数据结构(如链表),这可能导致额外的内存开销。
  • 查找效率下降:如果链表过长,查找操作的效率就会受到影响,最坏情况下变成 O(n)。
2. 开放寻址法(Open Addressing)

开放寻址法的基本思想是,当发生冲突时,哈希表会尝试找到一个空桶来存储冲突的元素,而不是使用链表存储所有冲突的元素。常见的开放寻址法有以下几种:

文章配图

线性探测(Linear Probing)

线性探测是一种常见的开放寻址方法,当发生冲突时,哈希表会按顺序探测下一个位置,直到找到一个空桶为止。具体步骤如下:

  • 计算哈希值,如果位置已经被占用,则尝试当前位置后的桶(即加 1、加 2、加 3…)。

优点:

  • 实现简单,直接。
  • 空间效率较高,因为不需要额外的数据结构。

缺点:

  • 聚集问题:线性探测会导致'聚集'现象,即多个元素集中在一小段区域内,导致查找效率降低。
  • 删除操作复杂:删除一个元素时,可能会导致查找操作失败,需要特别处理。

二次探测(Quadratic Probing)

二次探测是一种改进版的线性探测方法。当发生冲突时,探测过程遵循一个二次方程(如加 1^2、2^2、3^2…)。

优点:

  • 避免了线性探测的聚集问题。

缺点:

  • 仍然有聚集现象(虽然比线性探测要轻微)。
  • 可能导致表的大小不合适,影响性能。

四、哈希表的实际应用:C++ 实现

C++ 标准库提供了基于哈希表实现的容器,如**unordered_set和unordered_map,它们是无序的集合和映射容器。与set和map的区别在于,unordered_set和unordered_map**使用哈希表作为底层实现,因此查找、插入和删除操作具有常数时间复杂度。

1. 使用 unordered_set

**unordered_set**是一个哈希表实现的集合,存储的元素是唯一的,不允许重复。

#include <iostream>
#include <unordered_set>

int main() {
    std::unordered_set<int> mySet = {1, 2, 3, 4, 5};
    // 插入元素
    mySet.insert(6);
    // 查找元素
    if (mySet.find(3) != mySet.end()) {
        std::cout << "Found 3!" << std::endl;
    }
    // 删除元素
    mySet.erase(4);
    // 打印元素
    for (int element : mySet) {
        std::cout << element << " ";
    }
    std::cout << std::endl;
    return 0;
}

2. 使用 unordered_map

**unordered_map**是一个哈希表实现的映射容器,存储键值对。

#include <iostream>
#include <unordered_map>

int main() {
    std::unordered_map<std::string, int> myMap;
    // 插入元素
    myMap["Alice"] = 25;
    myMap["Bob"] = 30;
    // 查找元素
    if (myMap.find("Alice") != myMap.end()) {
        std::cout << "Alice's age: " << myMap["Alice"] << std::endl;
    }
    // 删除元素
    myMap.erase("Bob");
    // 打印键值对
    for (const auto& pair : myMap) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }
    return 0;
}

五、哈希表与红黑树的性能对比

下面是性能测试代码,包含大量重复、部分重复、完全有序三组测试用例,分别从插入、查找、删除三个维度进行对比。

注:测试性能用的是 Release 版,这里的基础数据量为 100 w

#include <iostream>
#include <vector>
#include <set>
#include <unordered_set>
using namespace std;

int main() {
    const size_t N = 1000000;
    unordered_set<int> us;
    set<int> s;
    vector<int> v;
    v.reserve(N);
    srand(time(0));
    for (size_t i = 0; i < N; ++i) {
        //v.push_back(rand()); //大量重复
        //v.push_back(rand()+i); //部分重复
        v.push_back(i); //完全有序
    }
    
    size_t begin1 = clock();
    for (auto e : v) {
        s.insert(e);
    }
    size_t end1 = clock();
    cout << "set insert:" << end1 - begin1 << endl;
    
    size_t begin2 = clock();
    for (auto e : v) {
        us.insert(e);
    }
    size_t end2 = clock();
    cout << "unordered_set insert:" << end2 - begin2 << endl;
    
    size_t begin3 = clock();
    for (auto e : v) {
        s.find(e);
    }
    size_t end3 = clock();
    cout << "set find:" << end3 - begin3 << endl;
    
    size_t begin4 = clock();
    for (auto e : v) {
        us.find(e);
    }
    size_t end4 = clock();
    cout << "unordered_set find:" << end4 - begin4 << endl << endl;
    
    cout << s.size() << endl;
    cout << us.size() << endl << endl;
    
    size_t begin5 = clock();
    for (auto e : v) {
        s.erase(e);
    }
    size_t end5 = clock();
    cout << "set erase:" << end5 - begin5 << endl;
    
    size_t begin6 = clock();
    for (auto e : v) {
        us.erase(e);
    }
    size_t end6 = clock();
    cout << "unordered_set erase:" << end6 - begin6 << endl << endl;
    
    return 0;
}

插入大量重复数据

文章配图

插入数据大量重复----结果:

  • 插入:哈希比红黑快 88%
  • 查找:哈希比红黑快 100%
  • 删除:哈希比红黑快 37%

插入部分重复数据

文章配图

插入数据部分重复----结果:

  • 插入:哈希与红黑差不多
  • 查找:哈希比红黑快 100%
  • 删除:哈希比红黑快 41%

插入完全有序数据

文章配图

插入数据完全有序----结果:

  • 插入:哈希比红黑慢 52%
  • 查找:哈希比红黑快 100%
  • 删除:哈希比红黑慢 58%

总的来说,在数据随机的情况下,哈希各方面都比红黑强,在数据有序的情况下,红黑更胜一筹。

总结

1. 操作性能差异
操作哈希表平均时间复杂度红黑树平均时间复杂度
查找O(1)O(log n)
插入O(1)O(log n)
删除O(1)O(log n)

从表中可以看出,哈希表的平均性能优于红黑树,尤其在查找和插入操作中,其常数时间复杂度具有明显优势。然而,红黑树的性能更为稳定,即使在最坏情况下,其时间复杂度依然为 O(log n)。

2. 空间效率对比
  • 哈希表:通常需要较大的内存来避免哈希冲突,因此在存储稀疏数据时可能造成一定的空间浪费。
  • 红黑树:作为平衡二叉树,其内存利用率较高,适合存储密集数据。
3. 数据顺序支持

哈希表中的数据存储是无序的,无法直接支持范围查询或按顺序遍历。而红黑树则天然支持有序性操作,如查找区间范围、按序遍历等。

结语

哈希表是现代计算机科学中不可或缺的数据结构,它通过哈希函数实现快速的数据存取,并广泛应用于各个领域。本文通过对哈希表的介绍,深入探讨了哈希函数的设计、哈希冲突的解决方法以及 C++ 中的实现,旨在帮助读者更好地理解和使用这一高效的数据结构。

在实际应用中,我们不仅要选择合适的哈希函数,还要灵活运用冲突解决策略,以确保哈希表能够高效地工作。在性能方面,哈希表通常优于红黑树,但在特定有序数据场景下红黑树表现更佳。

目录

  1. 一、哈希表概述
  2. 1. 什么是哈希表
  3. 2. 哈希表的优点
  4. 3. 哈希表的缺点
  5. 二、哈希函数
  6. 常见哈希函数
  7. 1. 直接定址法(常用)
  8. 2. 除留余数法(常用)
  9. 3. 平方取中法(了解)
  10. 4. 折叠法(了解)
  11. 5. 随机数法(了解)
  12. 三、哈希冲突的原因和解决方法
  13. 一、哈希冲突的原因
  14. 1. 哈希函数的设计问题
  15. 2. 输入数据的特点
  16. 3. 哈希表的大小和负载因子
  17. 二、哈希冲突的解决方法
  18. 1. 链表法(Separate Chaining)
  19. 2. 开放寻址法(Open Addressing)
  20. 四、哈希表的实际应用:C++ 实现
  21. 1. 使用 unordered_set
  22. 2. 使用 unordered_map
  23. 五、哈希表与红黑树的性能对比
  24. 插入大量重复数据
  25. 插入部分重复数据
  26. 插入完全有序数据
  27. 总结
  28. 1. 操作性能差异
  29. 2. 空间效率对比
  30. 3. 数据顺序支持
  31. 结语
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Qwen1.5-0.5B-Chat Web 界面开发技巧
  • 人工旅鼠算法 (ALA) 无人机三维路径规划研究与 Matlab 实现
  • 2023年12月GESP C++七级真题解析:商品交易
  • 前端国际化实现方案
  • 人形机器人躯干系统:结构方案与驱动设计
  • 大模型服务选型:AI Ping 性能评测与对比指南
  • MySQL 数据库基础:概念、架构与核心使用指南
  • OpenCode 开源 AI 编程助手使用指南
  • C 语言指针基础:内存寻址与变量访问
  • Llama-2-7b 昇腾 NPU 测评总结:核心性能数据与硬件选型参考
  • OpenClaw 结合 Kimi K2.5 实现本地私有化部署与办公自动化
  • Llama-3.2-3B 部署与 Ollama GPU 加速(CUDA/cuDNN)全流程
  • 极空间部署 Miloco 全屋 AI 自动化方案
  • OpenClaw 厂商全对比:主流 AI 智能体平台深度横评
  • 量化、算子融合、内存映射:C 语言实现 AI 推理优化
  • 黑客入门指南:零基础掌握核心安全能力与技能路径
  • C++红黑树实现与STL map底层原理
  • AI 时代如何脱颖而出:商业认知与行动策略
  • Qt 6 官方 C++ 类完整清单索引
  • MCP 协议详解:与 Function Call 的区别及使用方式

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online