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

C++ 数据结构:哈希表原理与 STL 实现详解

哈希表通过哈希函数将 Key 映射为数组下标,核心在于处理冲突。理解路径分为哈希值转换、哈希函数选择及冲突解决策略。常见冲突处理方式包括开放定址法(线性/二次探测)与哈希桶(链地址法)。C++ STL 中 unordered_map 与 unordered_set 底层均基于哈希桶模板类,通过提取键值的仿函数(ExtractKey)及自定义哈希策略(Hash)支持任意类型键。本文详细解析了哈希原理、经典算法及 STL 源码层面的封装逻辑。

RedisGeek发布于 2026/3/16更新于 2026/8/1745 浏览
C++ 数据结构:哈希表原理与 STL 实现详解

C++ 数据结构:哈希表原理与 STL 实现详解

哈希表基础

首先需要明确哈希(Hash)与哈希表(Hash Table)的区别。哈希是一种映射算法思想,而哈希表则是基于这种思想构建的数据结构。

哈希表的核心流程是统一的:获取一个 Key,通过哈希函数计算 Hasher(Key),得到存储位置并存放 Value;或者从该位置取出对应的 Value。

理解哈希表的三个层次

要深入理解哈希表,建议从以下三个层面递进学习:

  1. 哈希值:这是最基础的整数表示。
  2. 哈希函数:将哈希值映射到具体空间位置的方法。
  3. 哈希冲突:当多个 Key 映射到同一位置时的处理机制。

STL 底层哈希函数的本质是对哈希值进行二次 Hash。无论 Key 是 string、vector 还是其他自定义类型,最终都需要转换为整形哈希值才能被处理。因此,先获得哈希值,再使用哈希函数确定映射位置,是理解的关键逻辑链。

此外,哈希表在逻辑上可视为数组。数组有大小限制,当数据量超过容量或不同自定义类型的哈希值相同,必然产生哈希冲突。哈希是功能,冲突是结果,两者存在因果关系。

哈希值与转换策略

哈希函数通常针对整形数据进行操作(如除留余数法)。在使用 STL 时,若要对自定义类型进行哈希,必须提供将其转换为哈希值的策略(仿函数)。

转换策略需满足速度快、离散度高的要求。常见的策略包括:

  • BKDR 哈希:适用于字符串。
  • 异或组合:适用于简单容器。
  • hash_combine:适用于多成员复杂结构。

BKDR 哈希

优点是实现简单、计算快、离散度高。通过选取种子(如 131),对字符串每个字符进行处理。

size_t BKDRHash(const std::string &str) {
    size_t seed = 131; // 常用种子:31, 131, 1313...
    size_t hash = 1;
    for (auto e : str) {
        hash *= seed;
        hash += e;
    }
    return hash & 0x7FFFFFFF; // 确保返回正数
}

异或组合

适用于 vector<int> 等简单场景,实现简单但冲突率相对较高。

struct PointHash {
    size_t operator()(const std::vector<int> &vec) const {
        int hash = 0;
        for (auto e : vec) {
            hash ^= (e << 1);
        }
        return hash;
    }
};

hash_combine

推荐用于多成员结构体,冲突率低。

template <typename T>
void hash_combine(std::size_t& seed, const T& val) {
    seed ^= std::hash<T>()(val) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}

struct Person {
    std::string name;
    int age;
    bool operator==(const Person& p) const {
        return name == p.name && age == p.age;
    }
};

struct PersonHash {
    std::size_t operator()(const Person& p) const {
        std::size_t seed = 0;
        hash_combine(seed, p.name);
        hash_combine(seed, p.age);
        return seed;
    }
};

常见哈希函数

直接定址法

数学上的单调函数,每个哈希值对应唯一映射位置,无冲突。但仅适用于数据集中的情况,若数据分散则浪费空间。

除留余数法

最常用的方法。取模运算 key % p,其中 p 为不大于表长且接近表长的质数。例如表长为 10,可选 p=7。

文章配图

平方取中法

取关键字平方后的中间几位作为地址。例如 key=1234,平方为 1522756,取中间三位 227 作为地址。此法能较好打散连续键值。

基数转换法

将十进制转为其他进制(如十六进制),视作新数字后取模。若含字母则转 ASCII 码。

哈希冲突解决

负载因子(已插入数据个数 / 表大小)越大,冲突概率越高。负载因子通常控制在 0.7 以下。常见解决方案有两种:开放定址法和哈希桶。

开放定址法

冲突时向后偏移寻找空位。探测方式包括线性探测(逐个遍历)和二次探测(按平方数间隔)。

删除操作需注意标记状态。若直接清空,查找时会误判结束。因此需要 EMPTY(空)、EXIST(存在)、DELETE(已删除)三种状态。查找时需跳过 DELETE 继续向后,直到遇到 EMPTY。

template <class K, class V>
class HashData {
public:
    enum State { EMPTY, EXIST, DELETE };
    std::pair<K, V> _kv;
    State _state;
};

template <class K, class V>
class HashTable {
private:
    std::vector<HashData<K, V>> _tables;
    size_t _n = 0;

public:
    bool insert(const std::pair<K, V> &kv) {
        if (_tables.empty() || (_n * 10) / _tables.size() >= 7) {
            UpMemory();
        }
        if (Find(kv.first)) return false;

        int hashi = kv.first % _tables.size();
        while (_tables[hashi]._state == HashData<K, V>::State::EXIST) {
            hashi++;
            hashi %= _tables.size();
        }
        _tables[hashi]._kv = kv;
        _tables[hashi]._state = HashData<K, V>::State::EXIST;
        _n++;
        return true;
    }

    HashData<K, V>* Find(const K &key) {
        int hashi = key % _tables.size();
        int tmp = hashi;
        while (_tables[hashi]._state != HashData<K, V>::EMPTY) {
            if (_tables[hashi]._state == HashData<K, V>::EXIST && 
                _tables[hashi]._kv.first == key) {
                return &_tables[hashi];
            }
            hashi++;
            hashi %= _tables.size();
            if (hashi == tmp) return nullptr;
        }
        return nullptr;
    }

    bool Erase(const K &key) {
        HashData<K, V> *pdata = Find(key);
        if (!pdata) return false;
        pdata->_state = HashData<K, V>::State::DELETE;
        --_n;
        return true;
    }

private:
    void UpMemory() {
        int newsize = _tables.empty() ? 5 : _tables.size() * 2;
        HashTable<K, V> NewHT;
        NewHT._tables.resize(newsize);
        for (int i = 0; i < _tables.size(); ++i) {
            if (_tables[i]._state == HashData<K, V>::State::EXIST) {
                NewHT.insert(_tables[i]._kv);
            }
        }
        _tables.swap(NewHT._tables);
    }
};

哈希桶(链地址法)

哈希桶本质上是一个数组,每个元素挂接一个链表。冲突时创建节点链入该位置。STL 的 unordered_map 和 unordered_set 均基于此结构。

在负载因子为 1 时,随机数据的桶长度通常保持在常数级别,效率优于开放定址法。

unordered_map 与 unordered_set 的复用

为了共用底层哈希桶模板类,设计者巧妙利用了模板参数:

  • unordered_set 传入的 Value 类型为 Key。
  • unordered_map 传入的 Value 类型为 pair<Key, T>。

这样,查找和删除只需 Key,而插入时根据 Value 类型不同自然适配。

// 哈希桶定义
template<class Key, class Value, class Alloc, class ExtractKey, class Hash, class __Pred>
class HashBucket {
    // ... Find, Erase, Insert 实现 ...
};

// unordered_set 封装
template<class Key, class Hash, class Pred>
class unordered_set {
    typedef HashBucket<Key, Key, ..., KeyOfValue, Hash, Pred> HT;
    HT _ht;
    struct KeyOfValue { const Key& operator()(const Key& key) { return key; } };
    // ... 调用 _ht.Insert(data) ...
};

// unordered_map 封装
template<class Key, class T, class Hash, class Pred>
class unordered_map {
    typedef HashBucket<Key, std::pair<Key, T>, ..., KeyOfValue, Hash, Pred> HT;
    HT _ht;
    struct KeyOfValue { const Key& operator()(const std::pair<Key, T>& kv) { return kv.first; } };
    // ... 调用 _ht.Insert(data) ...
};
自定义类型键的处理

对于自定义类型,无法直接取模。需通过第五个模板参数 Hash 提供哈希值计算策略。同时,查找时需要比较两个对象是否相等,因此提供第六个模板参数 Pred(Equal 仿函数)。

底层插入伪代码如下:

insert(const Value &data) {
    KeyOfT key;
    int hashi = key(data) % _tables.size(); // 利用提取的 Key 计算哈希
    Node* newnode = new Node(data);
    newnode->_next = _tables[hashi];
    _tables[hashi] = newnode;
}

综上,哈希表的设计核心在于平衡空间与时间复杂度,而 STL 的实现则通过泛型编程最大化了复用性与灵活性。

目录

  1. C++ 数据结构:哈希表原理与 STL 实现详解
  2. 哈希表基础
  3. 理解哈希表的三个层次
  4. 哈希值与转换策略
  5. BKDR 哈希
  6. 异或组合
  7. hash_combine
  8. 常见哈希函数
  9. 直接定址法
  10. 除留余数法
  11. 平方取中法
  12. 基数转换法
  13. 哈希冲突解决
  14. 开放定址法
  15. 哈希桶(链地址法)
  16. unorderedmap 与 unorderedset 的复用
  17. 自定义类型键的处理
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • Drupal 7 核心模块与常用扩展功能梳理
  • 基于 Xilinx FPGA 的 RISC-V 五级流水线 CPU 设计实战
  • MCP+Skill 驱动的前端 JS 逆向自动化方案与工具实现
  • 在 Google Colab 中运行 LLaMA-13B 模型和 LangChain
  • AIGC 在日常生活中的应用、挑战与展望
  • 应对高 AIGC 率检测:学术论文写作与优化的技术解析
  • 英伟达与 GitHub 免费 AI 大模型 API Key 获取指南
  • MoonTV 开源跨平台影视聚合播放器
  • Python 库 addict 使用指南
  • Python Django Web 框架核心功能与实战案例
  • Python 破解行为检测与浏览器指纹反爬:从原理到实战
  • Stable Diffusion 扩散模型原理与 PyTorch 实现
  • 汽车雷达在多径存在下的幽灵目标检测——论文阅读
  • 海螺 AI 多模态架构与技术特性解析
  • 基于 Cursor 与 Playwright MCP 的 UI 自动化实践
  • 位运算算法专题:字符唯一性与缺失数字问题解析
  • Ego Planner 算法仿真环境与解耦架构实现
  • 华为鸿蒙及安卓手机谷歌验证器安装指南与替代方案
  • Visual Studio Code 2022 安装包下载及安装教程
  • Visual Studio 2022 无法使用 GitHub Copilot 的排查与解决

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如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