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

C++ 哈希表核心机制:unordered 系列容器、位图与布隆过滤器实战

深入解析 C++ 中 unordered_map 和 unordered_set 的底层哈希表实现。涵盖闭散列(线性探测)与开散列(链地址法)的冲突解决策略,以及迭代器模拟细节。进一步探讨位图在海量数据处理中的应用,如去重与交集计算,并介绍布隆过滤器的原理及其在空间效率上的优势。最后通过哈希切割解决大数据量下的文件交集问题,辅以经典面试题巩固理解。

清酒独酌发布于 2026/3/27更新于 2026/6/213 浏览
C++ 哈希表核心机制:unordered 系列容器、位图与布隆过滤器实战

unordered 系列关联式容器

unordered_map, unordered_set, unordered_multimap, unordered_multiset 均基于哈希表实现。它们的接口与 set/map 类似,支持范围 for 遍历。

主要区别:

  1. 迭代器为单向迭代器。
  2. 遍历时不保证有序。
  3. 性能通常优于红黑树实现的容器(如 std::set),但在数据已排序插入场景下,树结构可能更优。

注意:性能对比建议在 Release 模式下进行。

哈希基础

哈希(散列)通过建立值与存储位置的映射关系来提高查询效率,本质是以空间换时间。常用方法包括:

  1. 直接定址法:适用于值分布集中的场景(如统计字符出现次数)。
  2. 除留余数法:适用于值分布分散的场景,公式通常为 key % n。

哈希冲突处理

当不同键映射到同一位置时发生冲突,常见解决方案有:

  1. 闭散列(开放定址法):当前位置被占用时,按规则寻找下一个空位。包括线性探测和二次探测。
  2. 开散列(链地址法):即哈希桶,每个位置挂一个链表。

闭散列模拟实现

采用除留余数法配合线性探测。状态标记使用 EXIST, EMPTY, DELETE,其中 DELETE 用于解决删除后填充空洞的问题,扩容时需跳过 DELETE 状态。

enum STATE { EXIST, EMPTY, DELETE };

template<class K, class V>
struct HashData {
    pair<K, V> _kv;
    STATE _state = EMPTY;
};

template<class K>
struct DefaultHashFunc {
    size_t operator()(const K& key) {
        return (size_t)key;
    }
};

template<class K,  ,   = DefaultHashFunc<K>>
 HashTable {
:
    () { _table.(); }

    {
        
         (_n *  / _table.() >= ) {
             newSize = _table.() * ;
            HashTable<K, V, HashFunc> newHT;
            newHT._table.(newSize);
             ( i = ; i < _table.(); ++i) {
                 (_table[i]._state == EXIST) {
                    newHT.(_table[i]._kv);
                }
            }
            _table.(newHT._table);
        }

        HashFunc hf;
         hashi = (kv.first) % _table.();
         (_table[hashi]._state == EXIST) {
            ++hashi;
            hashi %= _table.();
        }
        _table[hashi]._kv = kv;
        _table[hashi]._state = EXIST;
        ++_n;
         ;
    }

    {
        HashFunc hf;
         hashi = (key) % _table.();
         (_table[hashi]._state != EMPTY) {
             (_table[hashi]._state == EXIST && _table[hashi]._kv.first == key) {
                 (HashData< K, V>*)&_table[hashi];
            }
            ++hashi;
            hashi %= _table.();
        }
         ;
    }

    {
        HashData< K, V>* ret = (key);
         (ret) {
            ret->_state = DELETE;
            --_n;
             ;
        }
         ;
    }

:
    vector<HashData<K, V>> _table;
     _n = ;
};
class
V
class
HashFunc
class
public
HashTable
resize
10
bool Insert(const pair<K, V>& kv)
// 负载因子控制,避免过满导致探测成本过高
if
10
size
7
size_t
size
2
resize
for
size_t
0
size
if
Insert
swap
size_t
hf
size
while
size
return
true
HashData<const K, V>* Find(const K& key)
size_t
hf
size
while
if
return
const
size
return
nullptr
bool Erase(const K& key)
const
Find
if
return
true
return
false
private
size_t
0

提示:类模板按需编译,未使用的成员函数不会生成代码。

开散列模拟实现

开散列使用指针数组存储链表头节点。负载因子达到 1 时触发扩容,将旧节点重新映射到新表。

template<class K, class T, class KeyOfT, class HashFunc = DefaultHashFunc<K>>
class HashTable {
    typedef HashNode<T> Node;
    template<class K, class T, class Ptr, class Ref, class KeyOfT, class HashFunc>
    friend struct HTIterator;

public:
    typedef HTIterator<K, T, T*, T&, KeyOfT, HashFunc> iterator;
    typedef HTIterator<K, T, const T*, const T&, KeyOfT, HashFunc> const_iterator;

    iterator begin() {
        for (size_t i = 0; i < _table.size(); ++i) {
            Node* cur = _table[i];
            if (cur) return iterator(cur, this);
        }
        return iterator(nullptr, this);
    }

    iterator end() { return iterator(nullptr, this); }

    ~HashTable() {
        for (size_t i = 0; i < _table.size(); ++i) {
            Node* cur = _table[i];
            while (cur) {
                Node* next = cur->_next;
                delete cur;
                cur = next;
            }
            _table[i] = nullptr;
        }
    }

    pair<iterator, bool> Insert(const T& data) {
        KeyOfT kot;
        iterator it = Find(kot(data));
        if (it != end()) return make_pair(it, false);

        HashFunc hf;
        if (_n == _table.size()) {
            size_t newSize = _table.size() * 2;
            vector<Node*> newTable(newSize, nullptr);
            for (size_t i = 0; i < _table.size(); ++i) {
                Node* cur = _table[i];
                while (cur) {
                    Node* next = cur->_next;
                    size_t hashi = hf(kot(cur->_data)) % newSize;
                    cur->_next = newTable[hashi];
                    newTable[hashi] = cur;
                    cur = next;
                }
                _table[i] = nullptr;
            }
            _table.swap(newTable);
        }

        size_t hashi = hf(kot(data)) % _table.size();
        Node* newnode = new Node(data);
        newnode->_next = _table[hashi];
        _table[hashi] = newnode;
        ++_n;
        return make_pair(iterator(newnode, this), true);
    }

    iterator Find(const K& key) {
        HashFunc hf;
        KeyOfT kot;
        size_t hashi = hf(key) % _table.size();
        Node* cur = _table[hashi];
        while (cur) {
            if (kot(cur->_data) == key) return iterator(cur, this);
            cur = cur->_next;
        }
        return end();
    }

    bool Erase(const K& key) {
        HashFunc hf;
        KeyOfT kot;
        size_t hashi = hf(key) % _table.size();
        Node* prev = nullptr;
        Node* cur = _table[hashi];
        while (cur) {
            if (kot(cur->_data) == key) {
                if (!prev) _table[hashi] = cur->_next;
                else prev->_next = cur->_next;
                --_n;
                delete cur;
                return true;
            }
            prev = cur;
            cur = cur->_next;
        }
        return false;
    }

private:
    vector<Node*> _table;
    size_t _n = 0;
};
迭代器模拟实现

迭代器需处理跨桶遍历逻辑。若当前桶链表结束,则查找下一个非空桶。

template<class K, class T, class Ptr, class Ref, class KeyOfT, class HashFunc>
struct HTIterator {
    typedef HashNode<T> Node;
    typedef HTIterator Self;
    typedef HTIterator<K, T, T*, T&, KeyOfT, HashFunc> Iterator;

    Node* _node;
    HashTable<K, T, KeyOfT, HashFunc>* _pht;

    HTIterator(Node* node, const HashTable<K, T, KeyOfT, HashFunc>* pht)
        : _node(node), _pht(const_cast<HashTable*>(pht)) {}

    HTIterator(const Iterator& it) : _node(it._node), _pht(it._pht) {}

    Ref operator*() { return _node->_data; }
    Ptr operator->() { return &_node->_data; }

    Self& operator++() {
        if (_node->_next) {
            _node = _node->_next;
        } else {
            KeyOfT kot;
            HashFunc hf;
            size_t hashi = hf(kot(_node->_data)) % _pht->_table.size();
            ++hashi;
            while (hashi < _pht->_table.size()) {
                if (_pht->_table[hashi]) {
                    _node = _pht->_table[hashi];
                    return *this;
                }
                ++hashi;
            }
            _node = nullptr;
        }
        return *this;
    }

    bool operator!=(const Self& s) { return _node != s._node; }
    bool operator==(const Self& s) { return _node == s._node; }
};

unordered_set 与 unordered_map 封装

基于上述哈希桶实现标准库容器。

namespace my_hash {
template<class K>
class unordered_set {
    struct SetKeyOfT {
        const K& operator()(const K& key) { return key; }
    };
public:
    typedef typename hash_bucket::HashTable<K, K, SetKeyOfT>::const_iterator iterator;
    typedef typename hash_bucket::HashTable<K, K, SetKeyOfT>::const_iterator const_iterator;

    iterator begin() { return _ht.begin(); }
    iterator end() { return _ht.end(); }

    pair<const_iterator, bool> insert(const K& key) {
        pair<typename hash_bucket::HashTable<K, K, SetKeyOfT>::iterator, bool> ret = _ht.Insert(key);
        return pair<const_iterator, bool>(ret.first, ret.second);
    }

private:
    hash_bucket::HashTable<K, K, SetKeyOfT> _ht;
};

template<class K, class V>
class unordered_map {
    struct MapKeyOfT {
        const K& operator()(const pair<K, V>& kv) { return kv.first; }
    };
public:
    typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
    typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;

    iterator begin() { return _ht.begin(); }
    iterator end() { return _ht.end(); }

    pair<iterator, bool> insert(const pair<K, V>& kv) { return _ht.Insert(kv); }

    V& operator[](const K& key) {
        pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));
        return ret.first->second;
    }

private:
    hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT> _ht;
};
}

位图(Bitmap)

利用比特位表示信息,常用于海量数据去重或存在性判断。标准库中对应 bitset。

应用场景

面试题:40 亿个无符号整数,快速判断某数是否存在。

  • 使用 set 或排序二分查找空间开销过大。
  • 位图仅需约 500MB 内存(40 亿 bit)。int 类型 32 位,每 32 个数占一个 int 单元。
template<size_t N>
class bitset {
public:
    bitset() { _a.resize(N / 32 + 1); }

    void set(size_t x) {
        size_t i = x / 32;
        size_t j = x % 32;
        _a[i] |= (1 << j);
    }

    void reset(size_t x) {
        size_t i = x / 32;
        size_t j = x % 32;
        _a[i] &= (~(1 << j));
    }

    bool test(size_t x) {
        size_t i = x / 32;
        size_t j = x % 32;
        return _a[i] & (1 << j);
    }

private:
    vector<int> _a;
};

注意:位图主要用于数值型数据的映射,字符串映射易冲突。

布隆过滤器(Bloom Filter)

利用多个独立哈希函数将元素特性映射到位图中。若所有对应位均为 1,则可能存在;若任一为 0,则一定不存在。

模拟实现

template<size_t N, class K, class Hash1, class Hash2, class Hash3>
class BloomFilter {
public:
    void Set(const K& key) {
        size_t hash1 = Hash1()(key) % N;
        _bs.set(hash1);
        size_t hash2 = Hash2()(key) % N;
        _bs.set(hash2);
        size_t hash3 = Hash3()(key) % N;
        _bs.set(hash3);
    }

    bool Test(const K& key) {
        size_t hash1 = Hash1()(key) % N;
        if (!_bs.test(hash1)) return false;
        size_t hash2 = Hash2()(key) % N;
        if (!_bs.test(hash2)) return false;
        size_t hash3 = Hash3()(key) % N;
        if (!_bs.test(hash3)) return false;
        return true;
    }

private:
    bitset<N> _bs;
};

限制:不支持直接删除(会导致误判),如需删除需引入引用计数。

哈希切割

将大文件数据根据哈希特征分片到多个小文件,解决单机内存不足问题。

应用案例

问题:两个 100 亿 query 文件,内存仅 1G,求交集。

  • 近似算法:布隆过滤器过滤。
  • 精确算法:哈希切割。两文件用相同哈希函数分片(A1-A10, B1-B10),相同 query 必落入同编号文件,逐对比较。

扩展:日志文件中找 Top K IP。

  • 分片后,每片维护一个小顶堆或 Map,最后汇总比较。

练习题

  1. 散列函数应使函数值按( )取其值域的每一个值。

    • A.最大概率 B.最小概率 C.同等概率 D.平均概率
    • 答案:C
  2. 解决散列冲突常采用的方法是( )。

    • A.数字分析法、除余法、平方取中法
    • B.数字分析法、除余法、线性探测法
    • C.数字分析法、线性探测法、多重散列法
    • D.线性探测法、多重散列法、链地址法
    • 答案:D
  3. 已知关键字序列(19, 14, 23...)散列存储于 H(key)=key%7 的表中,采用链地址法,等概率下查找成功的平均查找长度为( )。

    • 答案:A (1.5)
  4. 关于位图说法错误的是( )。

    • A.用比特位表示状态
    • B.可求集合交集
    • C.是哈希变形思想的应用
    • D.方便进行字符串映射及查找
    • 答案:D
  5. 关于 unordered_map/set 说法错误的是( )。

    • A.存储类型不同
    • B.底层结构相同
    • C.查找复杂度平均 O(1)
    • D.插入时都必须通过 key 比较找位置
    • 答案:D

参考题目:力扣 350. 两个数组的交集 II;力扣 884. 两句话中的不常见单词。

目录

  1. unordered 系列关联式容器
  2. 哈希基础
  3. 哈希冲突处理
  4. 闭散列模拟实现
  5. 开散列模拟实现
  6. 迭代器模拟实现
  7. unorderedset 与 unorderedmap 封装
  8. 位图(Bitmap)
  9. 应用场景
  10. 布隆过滤器(Bloom Filter)
  11. 模拟实现
  12. 哈希切割
  13. 应用案例
  14. 练习题
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • 黑客入门指南:零基础掌握核心安全能力与技能路径
  • 基于 C# 的 PLC 转 Web API 服务器框架实现
  • 前端开发进阶:AI 设计技能、工程最佳实践与硬件优化
  • 汽车参数对比爬虫实战:从静态页面到动态渲染的Python技术栈解析
  • Spring Boot 4.0 虚拟线程时代:WebFlux 与 WebMVC 选型分析
  • Git 分布式版本控制:安装、配置与实战指南
  • Yii 框架核心系统组件详解:日志、路由与缓存
  • Windows 11 安装 nvm 管理 Node.js 和 npm 多版本及切换
  • Flink 批计算单词统计示例
  • C++ STL 容器适配器详解:Stack、Queue 与 Priority Queue
  • 基于 SpringBoot 的网页时装购物系统设计与实现
  • 基于 LVS+Keepalived+NFS 的高可用 Web 集群构建与验证
  • 深入理解 C++ 多态:虚函数、重写与底层原理
  • uv 虚拟环境管理:venv 创建、激活与 Python 版本指定
  • 快速排序非递归实现详解
  • Spatial Joy 2025 全球 AR&AI 赛事:开发者资源、玩法与避坑攻略
  • Sora2 Pro API Python 接入指南:4K 视频生成实战
  • VS Code Copilot 完整使用教程(含图解)
  • Windows 11 下利用 Docker 搭建 CUDA 开发环境
  • 轻量级日历组件 Calendar.js 集成指南

相关免费在线工具

  • 加密/解密文本

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