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

C++ unordered_map/set 底层原理与位图布隆过滤器实现

C++ 中基于哈希表的无序关联式容器(unordered_map/set)的底层实现原理。内容涵盖哈希函数设计、哈希冲突解决策略(闭散列线性探测与开散列表地址法)、迭代器模拟及扩容机制。此外,详细讲解了位图(bitset)的数据压缩应用、布隆过滤器的存在性判断原理及其优化方案,以及哈希切割在海量数据处理中的实践。文中包含相关代码示例及经典面试题解析。

蜜桃汽水发布于 2026/3/30更新于 2026/9/871 浏览
C++ unordered_map/set 底层原理与位图布隆过滤器实现

unordered 系列关联式容器

unordered_map、unordered_set、unordered_multimap、unordered_multiset 均使用哈希表实现,接口与 set 类似,支持范围 for 遍历。

与 set 的区别:

  1. 迭代器为单向迭代器。
  2. 中序遍历结果无序。
  3. 性能通常优于 set,但在有序数据插入场景下 set 表现更好。

注意:性能比较应在 Release 模式下进行。

哈希

哈希(散列)是建立值与存储位置对应关系的方法,以空间换时间。常用方法包括直接定址法和除留余数法。

哈希冲突

不同值映射到相同位置即发生冲突。解决策略:

  1. 闭散列(开放定址法):当前位置被占用时按规则寻找下一个位置(如线性探测、二次探测)。
  2. 开散列(链地址法/哈希桶):每个位置挂一个链表。
闭散列的模拟实现

采用除留余数法加线性探测。状态标记使用 EXIST、EMPTY、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, class V, class HashFunc = DefaultHashFunc<K>>
class HashTable {
public:
    HashTable() { _table.resize(10); }

    bool Insert(const pair<K, V>& kv) {
        if (_n * 10 / _table.size() >= 7) {
            size_t newSize = _table.size() * 2;
            HashTable<K, V, HashFunc> newHT;
            newHT._table.resize(newSize);
            for (size_t i = 0; i < _table.size(); ++i) {
                if (_table[i]._state == EXIST) {
                    newHT.Insert(_table[i]._kv);
                }
            }
            _table.swap(newHT._table);
        }

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

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

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

private:
    vector<HashData<K, V>> _table;
    size_t _n = 0;
};

注意:扩容时 DELETE 状态的节点不迁移。负载因子影响冲突概率与空间利用率。

开散列的模拟实现

使用指针数组存储链表头节点,头插法插入新节点。

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

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

    Node* 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 == nullptr) _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<K, T, Ptr, Ref, KeyOfT, HashFunc> 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(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;
                } else {
                    ++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 的封装

namespace algo_lib {
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;
};
}

unordered_map 的封装

namespace algo_lib {
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(); }
    const_iterator begin() const { return _ht.begin(); }
    const_iterator end() const { 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;
};
}

位图

利用比特位表达信息,库中对应 bitset,常用接口:test, set, reset。

应用

面试题: 40 亿个不重复无符号整数,快速判断某数是否存在。 使用位图节省空间。int 类型 32 位,需 size_t 范围的比特位。

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

交集问题: 两个文件各 100 亿整数,内存 1G。可用位图存 42 亿多整数(约 512MB),分别映射后比对。

布隆过滤器

利用多个独立哈希函数 + 位图实现高效存在性判断。若所有位置均为 1,则可能存在;否则一定不在。

应用场景:快速判断昵称是否注册过,减轻数据库压力。

布隆过滤器的模拟实现

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) == false) return false;
        size_t hash2 = Hash2()(key) % N;
        if (_bs.test(hash2) == false) return false;
        size_t hash3 = Hash3()(key) % N;
        if (_bs.test(hash3) == false) return false;
        return true;
    }

private:
    bitset<N> _bs;
};

注意:一般不支持删除操作,否则会导致误判。如需删除可使用引用计数。

优化参数 k(哈希函数个数)、m(长度)、n(元素个数)。

哈希切割

运用哈希函数将大文件数据分到多个小文件。

哈希切割的应用

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

  • 近似算法: 布隆过滤器。
  • 精确算法: 哈希切割。相同 query 进入相同编号文件(如 A1 和 B1)。若内存不足,可二次切分。

求 Top K IP: 每个小文件保留前 K 多地址到堆和 map,最后汇总比较。

作业部分

  1. 散列函数应同等概率取值域每一个值。(C)
  2. 解决冲突方法:线性探测、多重散列、链地址法。(D)
  3. 平均查找长度计算:总查找长度除以元素个数。
  4. 二次探测再散列 n 个关键字至少探测次数:n(n+1)/2。(E)

力扣 350. 两个数组的交集 II 核心逻辑:统计频次,取最小值,处理重复。

class Solution {
public:
    vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
        unordered_map<int, int> hash1;
        unordered_map<int, int> hash2;
        vector<int> v;
        for (auto e : nums1) hash1[e]++;
        for (auto e : nums2) hash2[e]++;
        for (auto e : nums1) {
            if (hash1.count(e) && hash2.count(e)) {
                for (int i = 0; i < min(hash1[e], hash2[e]); ++i) {
                    v.push_back(e);
                }
                hash1[e] = 0;
                hash2[e] = 0;
            }
        }
        return v;
    }
};

位图说法错误: D. 位图可以很方便的进行字符串的映射以及查找(易冲突)。

力扣 884. 两句话中的不常见单词 注意空字符串处理,count 返回出现次数仅适用于 multimap/multiset。

unordered_map/set 说法错误: D. 插入时都得通过 key 的比较去找待插入元素的位置(哈希表直接定位)。

目录

  1. unordered 系列关联式容器
  2. 哈希
  3. 哈希冲突
  4. 闭散列的模拟实现
  5. 开散列的模拟实现
  6. 哈希桶里面迭代器的模拟实现
  7. unordered_set 的封装
  8. unordered_map 的封装
  9. 位图
  10. 应用
  11. 布隆过滤器
  12. 布隆过滤器的模拟实现
  13. 哈希切割
  14. 哈希切割的应用
  15. 作业部分

更多推荐文章

查看全部
  • TanStack Table 核心特性与选型分析
  • 前端实现记住密码功能的原理与方案
  • WebMCP:开启 Agentic Web 新时代——Chrome 新 API 的特性与前瞻
  • 文件上传同名冲突检测的前后端协同实现方案
  • FunASR 离线文件转写服务开发指南(实践篇)
  • CLI-Anything:让所有软件都能被 AI Agent 原生调用
  • Linux 网络传输层 TCP 协议基础
  • MySQL 表约束核心指南:从基础到外键实战
  • 7 款热门 Claude Skills 开源项目解析:自动化工作流与代码审查实战
  • Z-Image-Turbo 与 Stable Diffusion 实测:速度提升 4 倍,质量如何?
  • C++ STL 算法库核心用法与实战
  • Kubernetes Informer Indexer 索引机制深度解析
  • CentOS 7 yum 配置阿里云镜像源
  • OpenClaw 多飞书机器人配置指南
  • Qwen3-4B-Instruct 实战:HR 招聘 JD 生成与面试题库构建
  • 华为 OD 机试双机位 C 卷:补种未成活胡杨
  • Python 程序员接私活的可靠途径与项目执行指南
  • 8 个实用的 Python 自动化办公脚本示例
  • 鸿蒙电商购物车实战:用户管理、商品列表与购物车功能实现
  • 耳机阻抗与前端适配:32Ω、150Ω、300Ω 耳机的推力需求解析

相关免费在线工具

  • 加密/解密文本

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