跳到主要内容
封装哈希表实现 unordered_set/unordered_map | 极客日志
C++ 算法
封装哈希表实现 unordered_set/unordered_map 哈希表通过哈希函数建立关键字与存储位置的映射关系,实现快速查找。本文介绍哈希表概念、负载因子、哈希冲突及常见哈希函数设计方法。详细讲解直接定址法、开放定址法(线性探测、二次探测)及链地址法原理与实现。重点展示基于链地址法的哈希表封装代码,包含扩容策略、迭代器实现,并以此为基础封装模拟 STL 的 unordered_set 和 unordered_map 类。
BackendPro 发布于 2026/3/30 更新于 2026/4/23 1 浏览封装哈希表实现 unordered_set/unordered_map
一 STL 标准库中 unordered_set/unordered_map 的使用
1.1 参考文档
unordered_set 文档解析
unordered_map 文档解析
这两个的使用 set 和 map 一样,这里我只讲一讲它们的区别和效率上的差异。
二 哈希表的实现 (抓住映射)
2.1 哈希表的概念
哈希 (hash) 又称散列,是一种组织数据的方式。从译名来看,有散乱排列的意思。本质就是通过哈希函数把关键字 Key 跟存储位置建立一个映射关系,查找时通过这个哈希函数计算出 Key 存储的位置,进行快速查找。
2.2 哈希表的实现方法一直接定址法
顾名思义 哈希表中每个数据的储存位置是确定的,当关键字的范围比较集中时,直接定址法就是非常简单高效的方法 ,比如一组关键字都在 [0,99] 之间,那么我们开一个 100 个数的数组,每个关键字的值直接就是存储位置的下标。再比如一组关键字值都在 [a,z] 的小写字母,那么我们开一个 26 个数的数组,每个关键字 ascii 码 -a ascii 码就是存储位置的下标。也就是说直接定址法本质就是用关键字计算出一个绝对位置或者相对位置 。【例体统计字符串中字符的第一个唯一出现的个数,并且字符串中只有小写】这里我们就可以使用直接定址法因为小写字母只有 26 个很集中,这里我们开一个 26 个数的数组,每个关键字 ascii 码-a ascii 码就是存储位置的下标即可。字符串中的第一个唯一字符
【题解】
class Solution {
public :
int firstUniqChar (string s) {
int count[26 ] = {0 };
( ch : s) {
count[ch- ]++;
}
( i = ; i < s. (); ++i) {
(count[s[i]- ] == ) i;
}
;
}
};
for
auto
'a'
for
size_t
0
size
if
'a'
1
return
return
-1
上面这题使用了直接定址法实现,题目给的字符串说明了只包含小写字母,所以关键子 (数据) 的范围很集中。所以这种方法的使用很局限,只有在关键子的范围较集中时使用才效率高,当关键字的范围比较分散时,就很浪费内存甚至内存不够用 。
2.3 哈希表相关概念
2.3.1 哈希冲突 顾名思义是不同的关键字 (值) 映射到哈希表中的同一个位置,从而发生哈希冲突。即两个不同的 key 可能会映射到同一个位置去,这种问题我们叫做哈希冲突,或者哈希碰撞 。理想情况是找出一个好的哈希函数避免冲突,但是实际场景中,冲突是不可避免的,所以我们尽可能设计出优秀的哈希函数,减少冲突的次数,同时也要去设计出解决冲突的方案。哈希冲突会影响哈希表的效率,所以哈希冲突越多其效率越低。
2.3.2 负载因子 (由于扩容,注意不同方法的扩容条件不同) 假设哈希表中已经映射存储了 N 个值,哈希表的大小为 M,那么负载因子=N/M,负载因子有些地方也翻译为载荷因子/装载因子等。负载因子越大,哈希冲突的概率越高,空间利用率越高;负载因子越小,哈希冲突的概率越低,空间利用率越低 (即扩容在一定程度上也能减少哈希冲突)。
2.3.3 将关键字转为整数 由于哈希表对于 key 的要求是可以取模即可以转为整数。我们将关键字映射到数组中位置,一般是整数好做映射计算,如果不是整数,我们要想办法转换成整数,这个细节我们后面代码实现中再进行细节展示。下面哈希函数部分我们讨论时,如果关键字不是整数,那么我们讨论的 Key 是关键字转换成的整数。
2.4 哈希函数 (用于解决/降低哈希冲突) 一个好的哈希函数应该让 N 个关键字被等概率的均匀的散列分布到哈希表的 M 个空间中,但是实际中却很难做到,但是我们要尽量往这个方向去考量设计。哈希函数用于插入所以在查找时也要用这个哈希函数。所以一个哈希函数其计算 key 的映射位置每次要相同 (不可能插入时应该位置,查找时应该位置)。哈希函数是哈希原理:插入是用这个规则,查找时也用这个规则。
2.4.1 除法散列法/除留余数法
2.4.1.1 除法散列法/除留余数法的概念 除法散列法也叫做除留余数法,顾名思义,假设哈希表的大小为 M,那么通过 key 除以 M 的余数作为映射位置的下标,也就是哈希函数为:h(key) = key % M。h 就是 key 映射在哈希表中的位置 (实际要插入的位置。这里 只进行除所以对于表的大小没有要求,只是取值满足其规则即可 (即对 M 的要求)。
2.4.1.2 怎么设置出优秀的除法散列法/除留余数法 [规则]
• 当使用除法散列法时,要避免 M 为某些值,如 2 的幂,10 的幂等 。如果是 $2^x$,那么 key % 本质相当于保留 key 的后 X 位,那么后 x 位相同的值,计算出的哈希值都是一样的,就冲突了。如:{63 , 31} 看起来没有关联的值,如果 M 是 16,也就是 $2^4$,那么计算出的哈希值都是 15,因为 63 的二进制后 8 位是 00111111,31 的二进制后 8 位是 00011111。如果是 $10^x$,就更明显了,保留的都是 10 进值的后 x 位,如:{112, 12312},如果 M 是 100,也就是 $10^2$,那么计算出的哈希值都是 12。这里不能取2 的幂,10 的幂等,其实我们把 key 和 M 化成对应的进制就知道了,即只有后 x 为参与了计算,这就大大增加了发生哈希冲突的概率 (这里我们应该让 key 和 M 两者的位数尽可能的多参与方可减少哈希冲突的发生)。
• 当使用除法散列法时,建议 M 取不太接近 2 的整数次幂的一个质数 (素数)【其一定不是哪个数的幂)。注意在 实践中也是八仙过海,各显神通,不同语言可能也不同**。例如 Java 的 HashMap 采用除法散列法时就是 2 的整数次幂做哈希表的大小 M,这样玩的话,就不用取模,而可以直接位运算,相对而言位运算比模更高效一些。但是他不是单纯的去取模,比如 M 是 2^16 次方,本质是取后 16 位,那么用 key' = key>>16,然后把 key 和 key' 异或的结果作为哈希值。也就是说我们映射出的值还是在 [0,M) 范围内,但是本质都是尽量让 key 所有的位都参与计算,这样映射出的哈希值更均匀一些 即可。所以上面建议 M 取不太接近 2 的整数次幂的一个质数的理论是大多数数据结构书籍中写的理论吗,但是实践中,灵活运用,抓住本质即可。
2.4.2 乘法散列法(了解)
2.4.2.1 乘法散列法的概念 乘法散列法对哈希表大小 M 没有要求,他的思路第一步:用关键字 K 乘上常数 A (0<A<1),并抽取出 kA 的小数部分。第二步:后再用 M 乘以 k A 的小数部分,再向下取整。
即 h(key)=floor(M x ( (A x key) % 1.0)), 其中 floor 表示对表达式进行下取整,A∈(0,1)。本质就是算出一个和 key 有关的小数。
2.4.2.2 怎么设置出优秀的乘法散列法
• 这里最重要的是 A 的值应该如何设定 ,Knuth 认为 A 是黄金分割点 ((根号 5-1)/2=0.618......
• 注意乘法散列法对哈希表大小 M 是没有要求的,例如假设 M 为 1024,key 为 1234,A = 0.6180339887, Akey= 762.6539420558,取小数部分为 0.6539420558, M×((A×key)%1.0) = 0.6539420558 1024 = 669.6366651392,那么 h(1234) = 669。
2.4.3 全域散列法(了解)
• 如果存在一个恶意的对手,他针对我们提供的散列函数,特意构造出一个发生严重冲突的数据集,比如,让所有关键字全部落入同一个位置中。这种情况是可以存在的,只要散列函数是公开且确定的,就可以实现此攻击。解决方法自然是见招拆招,给散列函数增加随机性,攻击者就无法找出确定可以导致最坏情况的数据。这种方法叫做全域散列。
• $h_{ab}(key) = ((a \times key + b)%P )%M$,P 需要选一个足够大的质数,a 可以随机选 [1,P-1] 之间的任意整数,b 可以随机选 [0,P-1] 之间的任意整数,这些函数构成了一个 P*(P-1) 组全域散列函数组。假设 P=17,M=6,a = 3,b = 4, 则 $h_{34}(key) = ((3 \times key + 4)%17)%6$。$h_{34}(8) = ((3 \times 8 + 4)%17)%6 = 5$
• 需要注意的是每次初始化哈希表时,随机选取全域散列函数组中的一个散列函数使用,后续增删查改都固定使用这个散列函数,否则每次哈希都是随机选一个散列函数,那么插入是一个散列函数,查找又是另一个散列函数,就会导致找不到插入的 key 了。
• 这是乘法散列法的一个变形,本质还是除留余数法的一个哈希函数 ,这里是 key 经过一些运算之后再去取模。
2.4.4 其他方法(了解)
• 上面的几种方法是《算法导论》书籍中讲解的方法。
• 《殷人昆 数据结构:用面向对象方法与 C++ 语言描述(第二版)》和《[数据结构 (C 语言版)].严蔚敏_吴伟民》等教材型书籍上面还给出了平方取中法、折叠法、随机数法、数学分析法等,这些方法相对更适用于一些局限的特定场景,有兴趣可以去看看这些书籍。
2.4.5 处理哈希冲突的方法选择 实践中哈希表一般还是选择除法散列法作为哈希函数 (不是特殊情况不选择别的,例如为了防止别人攻击此时要选择全域散列法),但是哈希表无论选择什么哈希函数也避免不了冲突 ,那么插入数据时,如何解决冲突呢?这时候就要引入哈希表的实现方法,这里只讲主要有的两种方法,开放定址法和链地址法 ,看看它们如何降低甚至解决哈希冲突。
2.5 哈希表的实现方法二开放定址法
2.5.1 开放定址法的概念 在开放定址法中所有的元素都放到哈希表里,当一个关键字 key 用哈希函数计算出的位置冲突了,则按照某种规则找到一个没有存储数据的位置进行存储 ,开放定址法中负载因子一定是小于 1 (注意在实际中我们使用开发地址法实现哈希表时负载因子在 0.7 左右就要扩容,因为这样可以使得数据跟分散一些,在一定程度上化解了哈希冲突 )。这里的规则有三种:线性探测、二次探测、双重探测 。
2.5.2 开放定址法处理函数冲突的方法
2.5.2.1 线性探测
2.5.2.1.1 线性探测的规则 (概念)
• 从发生冲突的位置开始,依次线性向后探测,直到寻找到下一个没有存储数据的位置为止,如果走到哈希表尾,则回绕到哈希表头的位置。
• $h(key) = hash_0 = key % M$, hash0 位置冲突了,则线性探测公式为:$h_c(key,i) = hash_i = (hash_0 + i) % M$,$i = {1, 2, 3, ..., M - 1}$,因为负载因子小于 1,则最多探测 M-1 次,一定能找到一个存储 key 的位置。
• 线性探测的比较简单且容易实现,线性探测的问题假设,hash0 位置连续冲突,hash0,hash1,hash2 位置已经存储数据了,后续映射到 hash0,hash1,hash2,hash3 的值都会争夺 hash3 位置,这种现象叫做群集/堆积。为了改善这个问题而引入二次探测 。
• 这种方法的缺陷就是存在群集/堆积现象
2.5.2.1.2 线性探测的实例 下面演示 {19,30,5,36,13,20,21,12} 等这一组值映射到 M=11 的表中。h(19) = 8,h(30) = 8,h(5) = 5,h(36) = 3,h(13) = 2,h(20) = 9,h(21) =10,h(12) = 1 先插入 19,插入到下标为 8 的节点,在插入 30,30 的插入位置是下标为 8 的节点,由于下标为 8 的节点是已经储存数据,此时就要不断判断下标 8 之后的节点是否为空,为空就插入,如果到表未就绕回到标头继续判断,后面的数据依次执行这个逻辑即可。【映射到哈希表中的数据图】
2.5.2.2 二次探测
2.5.2.2.1 二次探测规则 (概念)
• 从发生冲突的位置开始,依次左右按二次方跳跃式探测,直到寻找到下一个没有存储数据的位置为止,如果往右走到哈希表尾,则回绕到哈希表头的位置;如果往左走到哈希表头,则回绕到哈希表尾的位置;
• $h(key) = hash_0 = key % M$, hash0 位置冲突了,则二次探测公式为:$h_c(key,i) = hash_i = (hash_0 \pm i ^2) % M$,$i = {1, 2, 3, ...}$。这里是+或者-i^2 所以其分布更广,有的地方是先+i^2 再-i^2 不断重复。所以这里相比一线性探测在缓解哈希冲突上更优一些。
• 二次探测当 $hash_i = (hash_0 - i^2)%M$ 时,当 hashi<0 时,需要 hashi += M
2.5.2.2.2 二次探测的实例 下面演示 {19,30,52,63,11,22} 等这一组值映射到 M=11 的表中。h(19) = 8, h(30) = 8, h(52) = 8, h(63) = 8, h(11) = 0, h(22) = 0【映射到哈希表中的数据图】
2.5.2.3 双重散列(了解)
2.5.2.3.1 双重散列的规则 (概念)
• 第一个哈希函数计算出的值发生冲突,使用第二个哈希函数计算出一个跟 key 相关的偏移量值,不断往后探测,直到寻找到下一个没有存储数据的位置为止。
• $h_1(key) = hash_0 = key % M$, hash0 位置冲突了,则双重探测公式为:$h_c(key,i) = hash_i = (hash_0 + i * h_2(key)) % M$,$i = {1, 2, 3, ..., M}$。
• 要求$h_2(key) < M$ 且 $h_2(key)$和 M 互为质数,有两种简单的取值方法:1、当 M 为 2 整数幂时,$h_2(key)$从从 [0,M-1] 任选一个奇数;2、当 M 为质数时,$h_2(key) = key % (M - 1) + 1$
• 保证$h_2(key)$与 M 互质是因为根据固定的偏移量所寻址的所有位置将形成一个群,若最大公约数$p = gcd(M, h_1(key)) > 1$,那么所能寻址的位置的个数为$M/P < M$,使得对于一个关键字来说无法充分利用整个散列表。
• 其本质是使用两个哈希函数
2.5.3 开放定址法实现哈希表的代码
inline unsigned long __stl_next_prime(unsigned long n) {
static const int __stl_num_primes = 28 ;
static const unsigned long __stl_prime_list[__stl_num_primes] = {
53 , 97 , 193 , 389 , 769 , 1543 , 3079 , 6151 , 12289 , 24593 , 49157 , 98317 ,
196613 , 393241 , 786433 , 1572869 , 3145739 , 6291469 , 12582917 , 25165843 ,
50331653 , 100663319 , 201326611 , 402653189 , 805306457 , 1610612741 ,
3221225473 , 4294967291
};
const unsigned long * first = __stl_prime_list;
const unsigned long * last = __stl_prime_list + __stl_num_primes;
const unsigned long * pos = lower_bound (first, last, n);
return pos == last ? *(last - 1 ) : *pos;
}
template <class K > struct HashFunc {
size_t operator () (const K& key) const {
return (size_t )key;
}
};
template <> struct HashFunc <string> {
size_t operator () (const string& key) const {
size_t hash = 0 ;
for (auto ch : key) {
hash += ch;
hash *= 131 ;
}
return hash;
}
};
struct pairHash {
size_t operator () (const pair<int , int >& kv) const {
size_t hash = 0 ;
hash += kv.first;
hash *= 131 ;
hash += kv.second;
hash *= 131 ;
cout << hash << endl;
return hash;
}
};
namespace open_adrress {
enum State { EXIST, EMPTY, DELETE };
template <class K , class V > struct HashData {
pair<K, V> _kv;
State _state = EMPTY;
};
template <class K , class V , class Hash = HashFunc<K>> class HashTable {
public :
HashTable (size_t n = 11 ) :_tables(n), _n(0 ) { }
bool Insert (const pair<K, V>& kv) {
if (Find (kv.first)) return false ;
if ((double )_n / (double )_tables.size () >= 0.7 ) {
HashTable<K, V, Hash> newht (__stl_next_prime(_tables.size() + 1 )) ;
for (size_t i = 0 ; i < _tables.size (); i++) {
if (_tables[i]._state == EXIST) {
newht.Insert (_tables[i]._kv);
}
}
_tables.swap (newht._tables);
}
Hash hs;
size_t hash0 = hs (kv.first) % _tables.size ();
size_t hashi = hash0;
size_t i = 1 ;
while (_tables[hashi]._state == EXIST) {
hashi = hashi + i;
i++;
hashi %= _tables.size ();
}
_tables[hashi]._kv = kv;
_tables[hashi]._state = EXIST;
++_n;
return true ;
}
HashData<K, V>* Find (const K& key) {
Hash hs;
size_t hash0 = key % _tables.size ();
size_t hashi = hash0;
size_t i = 1 ;
while (_tables[hashi]._state != EMPTY) {
if (_tables[hashi]._state == EXIST && _tables[hashi]._kv.first == key) {
return &_tables[hashi];
}
hashi = hashi + i;
i++;
hashi %= _tables.size ();
}
}
bool Erase (const K& key) {
HashData<K, V>* ret = Find (key);
if (ret) {
ret->_state = DELETE;
--_n;
return true ;
} else {
return false ;
}
}
private :
vector<HashData<K, V>> _tables;
size_t _n;
};
}
2.6 哈希表的实现方法三链地址法 开放定址法虽然在一定程度上缓解了哈希冲突,但是其有一个致命缺陷就一直再表中抢别人的位置 (无法真正解决哈希冲突,只能缓解)。为了彻底解决哈希冲突而引入链地址法。
2.6.1 链地址法解决冲突的思路 开放定址法 中所有的元素都放到哈希表里 ,链地址法 中所有的数据不再直接存储在哈希表中,哈希表中存储一个指针,没有数据映射这个位置时,这个指针为空,有多个数据映射到这个位置时,我们把这些冲突的数据链接成一个链表,挂在哈希表这个位置下面 ,链地址法也叫做拉链法或者哈希桶 (挂起来很像桶)。
2.6.2 扩容和怎么解决极端情况 (一个哈希表的位置 (一个桶很长))
2.6.2.1 扩容 开放定址法负载因子必须小于 1,链地址法的负载因子就没有限制了 (无哈希冲突),可以大于 1。负载因子越大,哈希冲突的概率越高,空间利用率越高;负载因子越小,哈希冲突的概率越低,空间利用率越低;stl 中 unordered_xxx 的最大负载因子基本控制在 1,大于 1 就扩容,所以下面实现也使用这种方式。当负载因子大于 1 就扩容。
2.6.2.2 解决极端情况 (一个哈希表的位置 (一个桶很长)) 其实我们可以考虑使用全域散列法,这样就不容易被针对了。但是假设不是被针对了,用了全域散列法,但是偶然情况下,某个桶很长,查找效率很低怎么办?这里我们可以参考在 Java8 的 HashMap 中当桶的长度超过一定阀值 (8) 时就把链表转换成红黑树 。
2.6.3 链地址法的代码实现
inline unsigned long __stl_next_prime(unsigned long n) {
static const int __stl_num_primes = 28 ;
static const unsigned long __stl_prime_list[__stl_num_primes] = {
53 , 97 , 193 , 389 , 769 , 1543 , 3079 , 6151 , 12289 , 24593 , 49157 , 98317 ,
196613 , 393241 , 786433 , 1572869 , 3145739 , 6291469 , 12582917 , 25165843 ,
50331653 , 100663319 , 201326611 , 402653189 , 805306457 , 1610612741 ,
3221225473 , 4294967291
};
const unsigned long * first = __stl_prime_list;
const unsigned long * last = __stl_prime_list + __stl_num_primes;
const unsigned long * pos = lower_bound (first, last, n);
return pos == last ? *(last - 1 ) : *pos;
}
template <class K > struct HashFunc {
size_t operator () (const K& key) const {
return (size_t )key;
}
};
template <> struct HashFunc <string> {
size_t operator () (const string& key) const {
size_t hash = 0 ;
for (auto ch : key) {
hash += ch;
hash *= 131 ;
}
return hash;
}
};
struct pairHash {
size_t operator () (const pair<int , int >& kv) const {
size_t hash = 0 ;
hash += kv.first;
hash *= 131 ;
hash += kv.second;
hash *= 131 ;
cout << hash << endl;
return hash;
}
};
namespace hash_bucket {
template <class K ,class V > struct HashNode {
pair<K, V> _kv;
HashNode<K, V>* _next;
HashNode (const pair<K,V>& kv) :_kv(kv),_next(nullptr ) { }
};
template <class K ,class V ,class Hash = HashFunc<K>> class HashTable {
typedef HashNode<K, V> Node;
public :
HashTable (size_t n = 11 ) :_tables(n, nullptr ), _n(0 ) { }
bool Insert (const pair<K, V>& kv) {
if (Find (kv.first)) return false ;
Hash hs;
if (_n == _tables.size ()) {
vector<Node*> newtables (__stl_next_prime(_tables.size() + 1 ), nullptr ) ;
for (size_t i = 0 ; i < _tables.size (); i++) {
Node* cur = _tables[i];
while (cur) {
Node* next = cur->_next;
size_t hashi = hs (cur->_kv.first) % newtables.size ();
cur->_next = newtables[hashi];
newtables[hashi] = cur;
cur = next;
}
_tables[i] = nullptr ;
}
_tables.swap (newtables);
}
size_t hashi = hs (kv.first) % _tables.size ();
Node* newnode = new Node (kv);
newnode->_next = _tables[hashi];
_tables[hashi] = newnode;
++_n;
return true ;
}
Node* Find (const K& key) {
Hash hs;
size_t hashi = hs (key) % _tables.size ();
Node* cur = _tables[hashi];
while (cur) {
if (cur->_kv.first == key) return cur;
cur = cur->_next;
}
}
bool Erase (const K& key) {
Hash hs;
size_t hashi = hs (key) % _tables.size ();
Node* prev = nullptr ;
Node* cur = _tables[hashi];
while (cur) {
if (cur->_kv.first == key) {
if (prev == nullptr ) {
_tables[hashi] = cur->_next;
} else {
prev->_next = cur->_next;
}
--_n;
delete cur;
return true ;
}
prev = cur;
cur = cur->_next;
}
return false ;
}
private :
vector<Node*> _tables;
size_t _n;
};
}
三 封装哈希表实现 unordered_set/unordered_map 这里和封装红黑树实现 set/map 的步骤差不多,可能出现的错误也差不多,可能因为对于 key 的要求不同为了满足这些要求可能要实现一些仿函数等等,导致模板参数不同。例如这里为了 key 可以取整所以要实现一个仿函数实现不同对象可以取整这不就比封装红黑树实现 set/map 的模板参数多一个。
HashTable.h #pragma once
#include <iostream>
#include <vector>
using namespace std;
inline unsigned long __stl_next_prime(unsigned long n) {
static const int __stl_num_primes = 28 ;
static const unsigned long __stl_prime_list[__stl_num_primes] = {
53 , 97 , 193 , 389 , 769 , 1543 , 3079 , 6151 , 12289 , 24593 , 49157 , 98317 ,
196613 , 393241 , 786433 , 1572869 , 3145739 , 6291469 , 12582917 , 25165843 ,
50331653 , 100663319 , 201326611 , 402653189 , 805306457 , 1610612741 ,
3221225473 , 4294967291
};
const unsigned long * first = __stl_prime_list;
const unsigned long * last = __stl_prime_list + __stl_num_primes;
const unsigned long * pos = lower_bound (first, last, n);
return pos == last ? *(last - 1 ) : *pos;
}
template <class K > struct HashFunc {
size_t operator () (const K& key) const {
return (size_t )key;
}
};
template <> struct HashFunc <string> {
size_t operator () (const string& key) const {
size_t hash = 0 ;
for (auto ch : key) {
hash += ch;
hash *= 131 ;
}
return hash;
}
};
struct pairHash {
size_t operator () (const pair<int , int >& kv) const {
size_t hash = 0 ;
hash += kv.first;
hash *= 131 ;
hash += kv.second;
hash *= 131 ;
cout << hash << endl;
return hash;
}
};
namespace hash_bucket {
template <class T > struct HashNode {
T _data;
HashNode<T>* _next;
HashNode (const T& data) :_data(data),_next(nullptr ) { }
};
template <class K ,class T ,class KeyOft ,class Hash > class HashTable ;
template <class K ,class T ,class Ref ,class Ptr , class KeyOft ,class Hash > struct HTIterator {
typedef HashNode<T> Node;
typedef HashTable<K, T, KeyOft, Hash> HT;
typedef HTIterator<K, T, Ref, Ptr, KeyOft, Hash> Self;
Node* _node;
const HT* _pht;
HTIterator (Node* node, const HT* pht) :_node(node),_pht(pht) { }
Self& operator ++() {
if (_node->_next) {
_node = _node->_next;
} else {
KeyOft kot;
Hash hs;
size_t hashi = hs (kot (_node->_data)) % _pht->_tables.size ();
hashi++;
while (hashi < _pht->_tables.size ()) {
if (_pht->_tables[hashi]) {
_node = _pht->_tables[hashi];
break ;
}
++hashi;
}
if (hashi == _pht->_tables.size ()) {
_node = nullptr ;
}
}
return *this ;
}
Ref operator *() {
return _node->_data;
}
Ptr operator ->() {
return &_node->_data;
}
bool operator ==(const Self& s)const {
return _node == s._node;
}
bool operator !=(const Self& s)const {
return _node != s._node;
}
};
template <class K ,class T , class KeyOft , class Hash > class HashTable {
typedef HashNode<T> Node;
template <class K ,class T ,class Ref ,class Ptr ,class KeyOft ,class Hash > friend struct HTIterator ;
public :
typedef HTIterator<K, T, T&, T*, KeyOft, Hash> Iterator;
typedef HTIterator<K, T, const T&, const T*, KeyOft, Hash> ConstIterator;
HashTable (size_t n = __stl_next_prime(0 )) :_tables(n,nullptr ),_n(0 ) { }
~HashTable () {
for (size_t i = 0 ; i < _tables.size (); i++) {
Node* cur = _tables[i];
while (cur) {
Node* next = cur->_next;
delete cur;
cur = next;
}
_tables[i] = nullptr ;
}
}
Iterator Begin () {
if (_n == 0 ) return End ();
for (size_t i = 0 ; i < _tables.size (); i++) {
if (_tables[i]) {
return Iterator (_tables[i], this );
}
}
return End ();
}
Iterator End () {
return Iterator (nullptr , this );
}
ConstIterator Begin () const {
if (_n == 0 ) return End ();
for (size_t i = 0 ; i < _tables.size (); i++) {
if (_tables[i]) {
return ConstIterator (_tables[i], this );
}
}
return End ();
}
ConstIterator End () const {
return ConstIterator (nullptr , this );
}
pair<Iterator,bool > Insert (const T& data) {
KeyOft kot;
Iterator it = Find (kot (data));
if (it != End ()) return { it,false };
Hash hs;
if (_n == _tables.size ()) {
vector<Node*> newtables (__stl_next_prime(_tables.size() + 1 ), nullptr ) ;
for (size_t i = 0 ; i < _tables.size (); i++) {
Node* cur = _tables[i];
while (cur) {
Node* next = cur->_next;
size_t hashi = hs (kot (cur->_data)) % newtables.size ();
cur->_next = newtables[hashi];
newtables[hashi] = cur;
cur = next;
}
_tables[i] = nullptr ;
}
_tables.swap (newtables);
}
size_t hashi = hs (kot (data)) % _tables.size ();
Node* newnode = new Node (data);
newnode->_next = _tables[hashi];
_tables[hashi] = newnode;
++_n;
return { Iterator (newnode,this ),true };
}
Iterator Find (const K& key) {
Hash hs;
KeyOft kot;
size_t hashi = hs (key) % _tables.size ();
Node* cur = _tables[hashi];
while (cur) {
if (kot (cur->_data) == key) return Iterator (cur,this );
cur = cur->_next;
}
return End ();
}
bool Erase (const K& key) {
Hash hs;
KeyOft kot;
size_t hashi = hs (key) % _tables.size ();
Node* prev = nullptr ;
Node* cur = _tables[hashi];
while (cur) {
if (kot (cur->_data) == key) {
if (prev == nullptr ) {
_tables[hashi] = cur->_next;
} else {
prev->_next = cur->_next;
}
--_n;
delete cur;
return true ;
}
prev = cur;
cur = cur->_next;
}
return false ;
}
private :
vector<Node*> _tables;
size_t _n;
};
}
unordered_set #pragma once
#include "HashTable.h"
namespace wzy {
template <class K ,class Hash = HashFunc<K>> class unordered_set {
struct SetKeyOft {
const K& operator ()(const K& key) {
return key;
}
};
public :
typedef typename hash_bucket::HashTable<K, const K, SetKeyOft, Hash>::Iterator iterator;
typedef typename hash_bucket::HashTable<K, const K, SetKeyOft, Hash>::ConstIterator 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 K& key) {
return _ht.Insert (key);
}
iterator find (const K& key) {
return _ht.Find (key);
}
bool erase (const K& key) {
return _ht.Erase (key);
}
private :
hash_bucket::HashTable<K, const K, SetKeyOft,Hash> _ht;
};
void Print (const unordered_set<int >& s) {
unordered_set<int >::const_iterator it = s.begin ();
while (it != s.end ()) {
cout << *it << " " ;
++it;
}
cout << endl;
}
}
unordered_map #pragma once
#include "HashTable.h"
namespace wzy {
template <class K ,class V ,class Hash = HashFunc<K>> 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, Hash>::Iterator iterator;
typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOft, Hash>::ConstIterator 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);
}
iterator find (const K& key) {
return _ht.Find (key);
}
bool erase (const K& key) {
return _ht.Erase (key);
}
V& operator [](const K& key) {
pair<iterator, bool > ret = insert ({ key, V () });
return ret.first->second;
}
private :
hash_bucket::HashTable<K, pair<const K, V>, MapKeyOft,Hash> _ht;
};
}
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,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
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online