跳到主要内容C++ unordered_map/set 底层原理与位图布隆过滤器实现 | 极客日志C++算法
C++ unordered_map/set 底层原理与位图布隆过滤器实现
综述由AI生成C++ 中基于哈希表的无序关联式容器(unordered_map/set)的底层实现原理。内容涵盖哈希函数设计、哈希冲突解决策略(闭散列线性探测与开散列表地址法)、迭代器模拟及扩容机制。此外,详细讲解了位图(bitset)的数据压缩应用、布隆过滤器的存在性判断原理及其优化方案,以及哈希切割在海量数据处理中的实践。文中包含相关代码示例及经典面试题解析。
蜜桃汽水28 浏览 unordered 系列关联式容器
unordered_map、unordered_set、unordered_multimap、unordered_multiset 均使用哈希表实现,接口与 set 类似,支持范围 for 遍历。
与 set 的区别:
- 迭代器为单向迭代器。
- 中序遍历结果无序。
- 性能通常优于 set,但在有序数据插入场景下 set 表现更好。
注意:性能比较应在 Release 模式下进行。
哈希
哈希(散列)是建立值与存储位置对应关系的方法,以空间换时间。常用方法包括直接定址法和除留余数法。
哈希冲突
不同值映射到相同位置即发生冲突。解决策略:
- 闭散列(开放定址法):当前位置被占用时按规则寻找下一个位置(如线性探测、二次探测)。
- 开散列(链地址法/哈希桶):每个位置挂一个链表。
闭散列的模拟实现
采用除留余数法加线性探测。状态标记使用 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 {
(_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 = ;
};
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
注意:扩容时 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,最后汇总比较。
作业部分
- 散列函数应同等概率取值域每一个值。(C)
- 解决冲突方法:线性探测、多重散列、链地址法。(D)
- 平均查找长度计算:总查找长度除以元素个数。
- 二次探测再散列 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 的比较去找待插入元素的位置(哈希表直接定位)。
相关免费在线工具
- 加密/解密文本
使用加密算法(如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