哈希
目录
[一、unordered_map & unordered_set](#一、unordered_map & unordered_set)
一、unordered_map & unordered_set
unorderer_set的声明
cpp
template < class Key, //
unordered_set::key_type/value_type
class Hash = hash<Key>, // unordered_set::hasher
class Pred = equal_to<Key>, // unordered_set::key_equal
class Alloc = allocator<Key> // unordered_set::allocator_type
> class unordered_set;
unordered_set和set的差异


使用差异
- set要求Key支持小于比较
- unordered_set要求Key支持转成整型,并且支持等于比较
迭代器差异
- set是双向迭代器
- unordered_set是单向迭代器
- set底层是红黑树,迭代器遍历:有序+去重
- unordered_set底层是哈希表,迭代器遍历:无序+去重
性能差异
- 红黑树增删查改:O(logN)
- 哈希表增删查改:O(1)
性能测试
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <set>
#include <unordered_set>
int test_set2()
{
const size_t N = 100000000;
unordered_set<int> us;
set<int> s;
vector<int> v;
v.reserve(N);
srand(time(0));
for (size_t i = 0; i < N; ++i)
{
v.push_back(rand()); //N比较大时,重复值比较多
//v.push_back(rand() + i); //重复值相对少
//v.push_back(i); //没有重复,有序
}
// 21:15
size_t begin1 = clock();
for (auto e : v)
{
s.insert(e);
}
size_t end1 = clock();
cout << "set insert:" << end1 - begin1 << endl;
size_t begin2 = clock();
us.reserve(N);
for (auto e : v)
{
us.insert(e);
}
size_t end2 = clock();
cout << "unordered_set insert:" << end2 - begin2 << endl;
int m1 = 0;
size_t begin3 = clock();
for (auto e : v)
{
auto ret = s.find(e);
if (ret != s.end())
{
++m1;
}
}
size_t end3 = clock();
cout << "set find:" << end3 - begin3 << "->" << m1 << endl;
int m2 = 0;
size_t begin4 = clock();
for (auto e : v)
{
auto ret = us.find(e);
if (ret != us.end())
{
++m2;
}
}
size_t end4 = clock();
cout << "unorered_set find:" << end4 - begin4 << "->" << m2 << endl;
cout << "插入数据个数:" << s.size() << endl;
cout << "插入数据个数:" << us.size() << endl << endl;
size_t begin5 = clock();
for (auto e : v)
{
s.erase(e);
}
size_t end5 = clock();
cout << "set erase:" << end5 - begin5 << endl;
size_t begin6 = clock();
for (auto e : v)
{
us.erase(e);
}
size_t end6 = clock();
cout << "unordered_set erase:" << end6 - begin6 << endl << endl;
return 0;
}
int main()
{
test_set2();
return 0;
}

unordered_map和map的差异
使用差异
- map要求Key支持小于比较
- unordered_map要求Key支持转成整型,并且支持等于比较
迭代器差异
- map是双向迭代器
- nordered_map是单向迭代器
- map底层是红黑树,迭代器遍历:Key有序+去重
- unordered_map底层是哈希表,迭代器遍历:Key无序+去重
性能差异
- 红黑树增删查改:O(logN)
- 哈希表增删查改:O(1)
unordered_multimap/unordered_multiset
与multimap/multiset的功能完全类似,但支持Key冗余
差异也为Key的要求差异 ,iterator及其遍历顺序的差异 ,性能的差异
二、哈希表的实现
2.1.哈希的概念
**哈希(hash):**散列(散乱排列),一种组织数据的方式
本质: 通过哈希函数 把关键字Key 与存储位置 建立一个映射关系
2.2.直接定址法
当关键字的范围比较集中,并且为整型时,直接定址法就是简单高效的方法
**试题:**字符串中的第一个唯一字符
题目内容:
给定一个字符串s ,找到它的第一个不重复的字符,并返回它的索引
如果不存在,则返回-1
示例:
输入: s = "leetcode"
输出: 0
cpp
class Solution
{
public:
int firstUniqChar(string s)
{
int count[26] = {0};
//统计次数
for(auto ch : s)
{
count[ch - 'a']++;
}
for(size_t i = 0;i < s.size();++i)
{
if(count[s[i] - 'a'] == 1)
{
return i;
}
}
return -1;
}
};
2.3.哈希冲突(哈希碰撞)
**哈希冲突:**两个不同的key可能会映射到同一个位置
示例:3 % 200 == 3 203 % 200 == 3
**解决方案:**设计出优秀的哈希函数,减少冲突次数
2.4.负载因子(载荷因子/装载因子)
假设哈希表中已经映射存储了N个值,哈希表的大小为M
**负载因子(load factor):**N / M
- 负载因子越大,哈希冲突概率越高,空间利用率越高
- 负载因子越小,哈希冲突概率越低,空间利用率越低
2.5.将关键字转为整数
关键字映射到数组的位置,一般是整数好实现映射计算
如果关键字不为整数,那么就需要将关键字转换成整数
2.6.哈希函数
当关键字的范围比较分散时,会浪费内存,甚至导致内存不够用
假设数据范围为[0,9999]的N个值,映射到一个M个空间的数组
就需要借助哈希函数(hash function)
关键字key放到数组的h(key)位置,h计算出的值在[0,M)之间
(注:一般情况下M >= N)
**设计方向:**尽可能让N个关键字被等概率的均匀的散列分布到哈希表的M个空间中
2.6.1.除法散列法(除留余数法)
**哈希函数:**h(key) = key % M
假设哈希表的大小为M,通过key除以M的余数作为映射位置的下标
(注:需要避免M为2的幂、10的幂)
示例1:{63,31}
M取16(2^4),计算的哈希值都为15
63的二进制后八位:0011 1111
31的二进制后八位:0001 1111
所以都取的是:1111(15)
(注:十进制数模上2的幂次方数,取的是十进制数的二进制后的幂次方数位)
示例2:{112,12312}
M取100(10^2),计算的哈希值都为12
112的十进制后两位:12
12312的十进制后两位:12
所以都取的是:12(12)
建议M取不太接近2的整数次幂的一个质数(素数)
(注:质数是大于1的自然数,并且只能被1和自己整除)
java的HashMap采用除法散列法时用的2的整数次幂做哈希表的大小M
但是它通过进行位运算,取模得到n位后的值,再与n位前几位进行异或
int hash = key &(1 << n - 1)
hash = hash ^ (key >> (32 - n))
cpp
#pragma once
#include <iostream>
using namespace std;
#include <vector>
#include "math.h"
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 HashTable
{
public:
HashTable()
:_tables(pow(2,_m))
, _n(0)
{}
size_t HashFunc(const K& key)
{
size_t hash = key & (_tables.size() - 1);
hash ^= (key >> (32 - _m));
return hash;
}
bool Insert(const pair<K, V>& kv)
{
if (Find(kv.first))
{
return false;
}
if (_n * 10 / _tables.size() >= 7)
{
HashTable<K, V> newht;
++_m;
newht._tables.resize(pow(2, _m));
for (auto& data : _tables)
{
if (data._state == EXIST)
{
newht.Insert(data._kv);
}
}
_tables.swap(newht._tables);
}
size_t hash0 = HashFunc(kv.first);
size_t hashi = hash0;
size_t i = 1;
while (_tables[hashi]._state == EXIST)
{
hashi = (hash0 + i) % _tables.size();
++i;
}
_tables[hashi]._kv = kv;
_tables[hashi]._state = EXIST;
++_n;
return true;
}
HashData<K, V>* Find(const K& key)
{
size_t hash0 = HashFunc(key);
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 = (hash0 + i) % _tables.size();
++i;
}
return nullptr;
}
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;
size_t _m = 16;
};
2.6.2.乘法散列法
对哈希表的大小M没有要求,用关键字key乘一个常数A(0 < A < 1)
抽取出k * A 的小数部分,再用M乘k * A 的小数部分,向下取整
**哈希函数:**h(key) = floor (M * ((A * key) % 1.0 )
floor():对表达式进行向下取整
A:A∈(0,1),Knuth建议取黄金分割点(0.6180339887...)
**示例:**假设M为1024,key为1234,A取0.6180339887
A * key = 762.6539420558
取小数部分为0.6539420558
M * 0.6539420558 = 669.6366651392
floor(669.6366651392) = 669
由此可得:h(1234) = 669
2.6.3.全域散列法
针对散列函数,特意构造出一个发生严重冲突的数据集
哈希表的效率急剧降低,导致访问速度变慢
只要散列函数公开,就可以实现此攻击
为了防止这种攻击,可以使用全域散列
**哈希函数:**hab(key) = ((a * key + b) % P)% M
P:足够大的质数
a:随机选取[1,P-1]之间的任意整数
b:随机选取[0,P-1]之间的任意整数
这些函数构成了一个P * (P - 1)组的全域散列函数组
示例:假设P = 17,M = 6,a = 3,b = 4
h34(8) = ((3 * 8 + 4) % 17)% 6 = 5
分布式与集群
一台机器无法存储处理海量的数据,必须要采取分布式
将数据存储在多台机器(集群),进行存储与运算处理
**示例:**给一张照片生成一个ID(字符串),如何知晓照片存放在哪一台机器?
将照片ID转换为整型,然后模1024,得到存储的机器编号
2.7.哈希冲突的处理
2.7.1.开放定址法
所有元素都放到哈希表里,当关键字key用哈希函数计算出的位置发生冲突
照某种规则找到一个没有存储数据的位置进行存储,负载因子一定要小于1
2.7.1.1.线性探测
从发生冲突的位置开始,依次线性向后探测,直到寻找到下一个没有存储数据的位置为止
如果走到哈希表尾,则回绕到哈希表头的位置
h(key) = key % M = hash0,如果hash0位置冲突
**线性探测公式:**hc(key,i) = (hash0 + i) % M,i = {1,2,3,...,M - 1} = hashi
因为负载因子小于1,则最多探测M - 1次,一定能够找到一个存储key的位置
**线性探测问题:**群集(堆积)现象
hash0,hash1,hash2位置已经存储数据了
后续映射到hash0,hash1,hash2,hash3的值都会争夺hash3的位置
将{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

每个存储值的位置加一个状态标识,否则删除一些数据后,会影响后面冲突值的查找
比如:如果删除30,下标9的位置就会没有数据,不会进行探测,导致20查找失败
给每个位置加一个标识{EXIST,EMPTY,DELETE},这样删除30可以不用删除值
而是把状态改为DELETE,查找20时遇到EMPTY
**2.7.1.2.**二次探测
从发生冲突的位置开始,依次左右按二次方跳跃式探测,直到寻找到下一个没有存储数据的位置
如果往右走到哈希表尾,则回绕到哈希表头的位置
如果往左走到哈希表头,则回绕到哈希表尾的位置
h(key) = key % M = hash0,如果hash0位置冲突
**二次探测公式:**hc(key,i) = (hash0 ± i^2) % M,i = {1,2,3,...,M/2} = hashi
先加后减:+1,-1,+2,-2,+9,-9,+16,-16......
当hashi = (hash0 - i^2) % M,如果hashi < 0,需要hashi += M
cpp
#pragma once
#include <iostream>
using namespace std;
#include <vector>
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 HashTable
{
public:
HashTable()
:_tables(11)
,_n(0)
{}
bool Insert(const pair<K, V>& kv)
{
//保证没有数据冗余
if (Find(kv.first))
{
return false;
}
//写法1:
//负载因子大于等于0.7
//if (_n * 10 / _table.size() >= 7)
//{
// //扩容
// vector<HashData<K, V>> newtables(_table.size() * 2);
// //更新映射关系
// for (auto& data : _tables)
// {
// if (data._state == EXIST)
// {
// size_t hash0 = data._kv.first % newtables.size();
// //...
// }
// }
// _tables.swap(newtables);
//}
//写法2:
//负载因子大于等于0.7
if (_n * 10 / _tables.size() >= 7)
{
HashTable<K, V> newht;
newht._tables.resize(_tables.size() * 2);
for (auto& data : _tables)
{
if (data._state == EXIST)
{
newht.Insert(data._kv);
}
}
_tables.swap(newht._tables);
}
//计算映射值hash0
//用key值模上哈希表的size
//(注:这里不能用capacity)
size_t hash0 = kv.first % _tables.size();
size_t hashi = hash0;
size_t i = 1;
//int flag = 1;
//循环条件为当前值的状态为存在
while (_tables[hashi]._state == EXIST)
{
//线性探测
hashi = (hash0 + i) % _tables.size();
//二次探测将i变为i * i * flag
//if (hashi < _tables.size())
//{
// hashi += _table.size();
//}
//if (flag == 1)
//{
// flag = -1;
//}
//else
//{
// ++i;
// flag = 1;
//}
++i;
}
_tables[hashi]._kv = kv;
_tables[hashi]._state = EXIST;
++_n;
return true;
}
HashData<K, V>* Find(const K& key)
{
size_t hash0 = key % _tables.size();
size_t hashi = hash0;
size_t i = 1;
//循环条件为当前值的状态不为空
while (_tables[hashi]._state != EMPTY)
{
//如果当前key值相等,且数据存在,查找成功
if (_tables[hashi]._state == EXIST && _tables[hashi]._kv.first == key)
{
return &_tables[hashi];
}
//线性探测
hashi = (hash0 + i) % _tables.size();
++i;
}
return nullptr;
}
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倍扩容后就不为质数了)
SGI版本的哈希表给了一个近似2倍的质数表,每次从质数表中获取扩容后的大小
cpp
inline unsigned long __stl_next_prime(unsigned long n)
{
// Note: assumes long is at least 32 bits.
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;
}
Key不能取模问题
当key为string或Date等类型时,key无法取模
需要给HashTable增加一个仿函数,把key转换成一个可以取模的整型
如果key可以转换为整型,且不容易冲突(比如:地址),仿函数就用默认参数即可
如果key无法转换为整型(比如:string、Date),需要实现一个仿函数,转换为整型
**示例1:**string做哈希表的key
cpp
#pragma once
#include <iostream>
using namespace std;
#include <vector>
namespace open_address
{
enum State
{
EXIST,
EMPTY,
DELETE
};
template <class K, class V>
struct HashData
{
pair<K, V> _kv;
State _state = EMPTY;
};
template <class K>
struct HashFunc
{
size_t operator()(const K& key)
{
return (size_t)key;
}
};
template <>
struct HashFunc <string>
{
//字符串转换成整形,可以把字符串的ASCII码相加
//但是直接相加的话"abcd"和"bcad"的计算结果相同
//BKDR哈希的思路,用上次的计算结果去乘一个质数
//这个质数一般取31, 131等效果比较好
size_t operator()(const string& key)
{
size_t hash = 0;
for (auto e : key)
{
hash *= 131;
hash += e;
}
return hash;
}
};
template < class K, class V, class Hash = HashFunc<K>>
class HashTable
{
public:
HashTable()
{
_tables.resize(__stl_next_prime(0));
}
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;
}
bool Insert(const pair<K, V>& kv)
{
if (Find(kv.first))
{
return false;
}
if (_n * 10 / _tables.size() >= 7)
{
HashTable<K, V, Hash> newHT;
newHT._tables.resize(__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 hash;
size_t hash0 = hash(kv.first) % _tables.size();
size_t hashi = hash0;
size_t i = 1;
while (_tables[hashi]._state == EXIST)
{
hashi = (hash0 + i) % _tables.size();
++i;
}
_tables[hashi]._kv = kv;
_tables[hashi]._state = EXIST;
++_n;
return true;
}
HashData<K, V>* Find(const K& key)
{
Hash hash;
size_t hash0 = hash(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 = (hash0 + i) % _tables.size();
++i;
}
return nullptr;
}
bool Erase(const K& key)
{
HashData<K, V>* ret = Find(key);
if (ret == nullptr)
{
return false;
}
else
{
ret->_state = DELETE;
--_n;
return true;
}
}
private:
vector<HashData<K, V>> _tables;
size_t _n = 0;
};
}
测试文件
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include "HashTable.h"
int main()
{
const char* a1[] = { "abcd","bcad","aadd"};
open_address::HashTable<string, string, open_address::HashFunc<string>> ht1;
for (auto e : a1)
{
ht1.Insert({ e,e });
}
for (auto e : a1)
{
cout << e << endl;
}
cout << endl;
cout << open_address::HashFunc<string>()("abcd") << endl;
cout << open_address::HashFunc<string>()("bcad") << endl;
cout << open_address::HashFunc<string>()("aadd") << endl;
return 0;
}
**示例2:**Date做哈希表的key
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include "HashTable.h"
struct Date
{
int _year;
int _month;
int _day;
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
,_month(month)
,_day(day)
{}
bool operator==(const Date& d)
{
return _year == d._year && _month == d._month && _day == d._day;
}
};
struct DateHashFunc
{
size_t operator()(const Date& d)
{
size_t hash = 0;
hash += d._year;
hash *= 131;
hash += d._month;
hash *= 131;
hash += d._day;
hash *= 131;
return hash;
}
};
int main()
{
open_address::HashTable<Date, int> ht;
ht.Insert({ {2024,10,12},1 });
ht.Insert({ {2026,3,28},1 });
return 0;
}
注:
如果key值为自定义类型,必须要对等于进行运算符重载,string有自带的运算符重载,但Date没有
需要手写,等于的运算符重载在unordered_map中是用Pred仿函数进行包装
2.7.1.3.双重散列
第一个哈希函数计算出的值发生冲突
使用第二个哈希函数计算出一个与key相关的偏移量值
不断往后检测,直到寻找到下一个没有存储数据的位置为止
h1(key) = key % M = hash0,如果hash0位置冲突
**双重探测公式:**hc(key,i) = (hash0 + i * h2(key))% M,i = {1,2,3,...,M}
要求h2(key) < M 且 h2(key)与M互为质数
取值方法1:当M为2整数幂时,h2(key)从[0,M-1]任选一个奇数
取值方法2:当M为质数时,h2(key) = key % (M - 1) + 1
本质:跳跃探测,减少冲突堆积
将{19,30,52,74}这组值映射到M = 11的表中,则h2(key) = key % 10 + 1

(注:实际插入顺序为52,74,19,30,i从1开始,每插入一个数加1)
2.7.2.链地址法
**开放定址法的缺陷:**发生哈希冲突后,会占领别人的位置
链地址法的思路:
哈希表中存储一个指针
没有数据映射这个位置时,这个指针为空
有多个数据映射这个位置,把冲突数据链接成一个链表(或者红黑树)
挂在哈希表的映射位置下面,这个方法也叫拉链法或者哈希桶
将{19,30,5,36,13,20,21,12,24,96}这一组值映射到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 h(24) = 2 h(96) = 8

扩容问题
开放定址法的负载因子必须小于1
链地址法的负载因子可以大于1
- 负载因子越大,哈希冲突的概率越高,空间利用率越高
- 负载因子越小,哈希冲突的概率越低,空间利用率越低
STL中哈希的最大负载因子基本控制在1,大于1就扩容
当某个桶过长时,可以使用全域散列法,也可以将链表转为红黑树
cpp
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>
struct HashFunc
{
size_t operator()(const K& key)
{
return (size_t)key;
}
};
template < class K, class V, class Hash = HashFunc<K>>
class HashTable
{
typedef HashNode<K, V> Node;
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;
}
public:
HashTable()
{
_tables.resize(__stl_next_prime(0), nullptr);
}
~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;
}
}
bool Insert(const pair<K, V>&kv)
{
if (Find(kv.first))
{
return false;
}
Hash hs;
size_t hashi = hs(kv.first) % _tables.size();
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);
}
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;
}
return nullptr;
}
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;
}
delete cur;
--_n;
return true;
}
prev = cur;
cur = cur->_next;
}
return false;
}
private:
vector<Node*> _tables; //指针数组
size_t _n = 0; //表中存储数据个数
};
}
三、哈希封装
3.1.源码及框架分析
SGI-STL30版本的哈希表是C++11之前的STL版本
unordered_map和unordered_set是C++11后更新的
它的容器名字叫hash_map和hash_set,为非标准容器
hash_map和hash_set的实现结构框架核心
cpp
// stl_hash_set
template < class Value, class HashFcn = hash<Value>,
class EqualKey = equal_to<Value>,class Alloc = alloc>
class hash_set
{
private:
typedef hashtable<Value, Value, HashFcn, identity<Value>,
EqualKey, Alloc> ht;
ht rep;
public:
typedef typename ht::key_type key_type;
typedef typename ht::value_type value_type;
typedef typename ht::hasher hasher;
typedef typename ht::key_equal key_equal;
typedef typename ht::const_iterator iterator;
typedef typename ht::const_iterator const_iterator;
hasher hash_funct() const { return rep.hash_funct(); }
key_equal key_eq() const { return rep.key_eq(); }
};
// stl_hash_map
template < class Key, class T, class HashFcn = hash<Key>,
class EqualKey = equal_to<Key>,class Alloc = alloc>
class hash_map
{
private:
typedef hashtable<pair<const Key, T>, Key, HashFcn,
select1st<pair<const Key, T> >, EqualKey, Alloc> ht;
ht rep;
public:
typedef typename ht::key_type key_type;
typedef T data_type;
typedef T mapped_type;
typedef typename ht::value_type value_type;
typedef typename ht::hasher hasher;
typedef typename ht::key_equal key_equal;
typedef typename ht::iterator iterator;
typedef typename ht::const_iterator const_iterator;
};
// stl_hashtable.h
template < class Value, class Key, class HashFcn,class ExtractKey, class EqualKey,class Alloc>
class hashtable {
public:
typedef Key key_type;
typedef Value value_type;
typedef HashFcn hasher;
typedef EqualKey key_equal;
private:
hasher hash;
key_equal equals;
ExtractKey get_key;
typedef __hashtable_node<Value> node;
vector<node*, Alloc> buckets;
size_type num_elements;
public:
typedef __hashtable_iterator<Value, Key, HashFcn, ExtractKey, EqualKey,Alloc> iterator;
pair<iterator, bool> insert_unique(const value_type& obj);
const_iterator find(const key_type & key) const;
};
template <class Value>
struct __hashtable_node
{
__hashtable_node * next;
Value val;
};
3.2.实现哈希表
HashTable.h
cpp
#include <iostream>
using namespace std;
#include <vector>
template <class K>
struct HashFunc
{
size_t operator()(const K & key)
{
return (size_t)key;
}
};
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
{
typedef HashNode<T> Node;
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;
}
public:
HashTable()
{
_tables.resize(__stl_next_prime(_tables.size()), nullptr);
}
~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;
}
}
bool Insert(const T & data)
{
KeyOfT kot;
if (Find(kot(data)))
{
return false;
}
Hash hs;
size_t hashi = hs(kot(data)) % _tables.size();
if (_n == _tables.size())
{
vector<Node*> newtables(__stl_next_prime(_tables.size()),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);
}
Node * newnode = new Node(data);
newnode->_next = _tables[hashi];
_tables[hashi] = newnode;
++_n;
return true;
}
private:
vector<Node*> _tables; // 指针数组
size_t _n = 0; // 表中存储数据个数
};
}
3.3.封装框架
UnorderedSet.h
cpp
#pragma once
#include "HashTable.h"
namespace bit
{
template<class K,class Hash = HashFunc<K>>
class unordered_set
{
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
public:
bool insert(const K& key)
{
return _ht.Insert(key);
}
private:
hash_bucket::HashTable<K, K, SetKeyOfT> _ht;
};
}
UnorderedMap.h
cpp
#pragma once
#include "HashTable.h"
namespace bit
{
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:
bool insert(const pair<K, V>&kv)
{
return _ht.Insert(kv);
}
private:
hash_bucket::HashTable<K, pair<K, V>, MapKeyOfT> _ht;
};
}
3.4.实现迭代器
iterator核心源码
cpp
template < class Value, class Key, class HashFcn, class ExtractKey, class EqualKey, class Alloc>
struct __hashtable_iterator
{
typedef hashtable < Value, Key, HashFcn, ExtractKey, EqualKey, Alloc> hashtable;
typedef __hashtable_iterator < Value, Key, HashFcn,ExtractKey, EqualKey, Alloc> iterator;
typedef __hashtable_const_iterator < Value, Key, HashFcn, ExtractKey, EqualKey, Alloc> const_iterator;
typedef __hashtable_node<Value> node;
typedef forward_iterator_tag iterator_category;
typedef Value value_type;
node * cur;
hashtable * ht;
__hashtable_iterator(node * n, hashtable * tab) : cur(n), ht(tab) {}
__hashtable_iterator() {}
reference operator*() const { return cur->val; }
#ifndef __SGI_STL_NO_ARROW_OPERATOR
pointer operator->() const { return &(operator*()); }
#endif /* __SGI_STL_NO_ARROW_OPERATOR */
iterator & operator++();
iterator operator++(int);
bool operator==(const iterator& it) const { return cur == it.cur; }
bool operator!=(const iterator& it) const { return cur != it.cur; }
};
template <class V, class K, class HF, class ExK, class EqK, class A>
__hashtable_iterator<V, K, HF, ExK, EqK, A>&
__hashtable_iterator<V, K, HF, ExK, EqK, A>::operator++()
{
const node* old = cur;
cur = cur->next;
if (!cur)
{
size_type bucket = ht->bkt_num(old->val);
while (!cur && ++bucket < ht->buckets.size())
{
cur = ht->buckets[bucket];
}
}
return *this;
}
3.5.链地址法总代码
HashTable.h
cpp
#pragma once
#include <iostream>
using namespace std;
#include <vector>
//key转整型仿函数
template <class K>
struct HashFunc
{
size_t operator()(const K & key)
{
return (size_t)key;
}
};
//key为string类型转整型的特化
template <>
struct HashFunc <string>
{
size_t operator()(const string & key)
{
size_t hash = 0;
for (auto e : key)
{
hash *= 131;
hash += e;
}
return hash;
}
};
namespace hash_bucket
{
//哈希节点
template <class T>
struct HashNode
{
T _data;//节点值 set:key map:key + value
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 HTIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;
//创建哈希节点
Node* _node;
//创建哈希迭代器
const HashTable<K, T, KeyOfT, Hash>* _pht;
//迭代器构造
HTIterator(Node* node, const HashTable<K, T, KeyOfT, Hash>* pht)
:_node(node)
,_pht(pht)
{}
//解引用重载
Ref operator*()
{
return _node->_data;
}
//引用重载
Ptr operator->()
{
return &_node->_data;
}
//不等号重载
bool operator!=(const Self& s)
{
return _node != s._node;
}
//前置++重载
Self& operator++()
{
//如果下一个节点不为空
if (_node->_next)
{
//将节点更新为下一个节点
_node = _node->_next;
}
else//如果下一个节点为空
{
KeyOfT kot;
Hash hash;
//计算映射值
size_t hashi = hash(kot(_node->_data)) % _pht->_tables.size();
//去下一个桶查找
++hashi;
//循环条件为走到最后一个桶之后
while (hashi < _pht->_tables.size())
{
//存储当前桶的头节点
_node = _pht->_tables[hashi];
//如果这个桶的头节点不为空
if (_node)
{
//查找成功,退出循环
break;
}
//继续查找
++hashi;
}
//如果所有桶都遍历结束
if (hashi == _pht->_tables.size())
{
//将哈希节点设为空
_node = nullptr;
}
else//如果当前桶有节点
{
//将这个节点赋给哈希节点
_node = _pht->_tables[hashi];
}
}
return *this;
}
};
template <class K, class T, class KeyOfT, class Hash>
class HashTable
{
//哈希迭代器模板的友元声明
template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
friend struct HTIterator;
//哈希节点的重命名
typedef HashNode<T> Node;
public:
//哈希迭代器的重命名
typedef HTIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;
typedef HTIterator<K, T, const T&,const T*, KeyOfT, Hash> ConstIterator;
Iterator Begin()
{
//如果数据个数为0
if (_n == 0)
{
return End();
}
//遍历哈希表
for (size_t i = 0; i < _tables.size(); i++)
{
Node * cur = _tables[i];
//查找到第一个有效数据
if (cur)
{
//返回第一个节点
return Iterator(cur, 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++)
{
Node * cur = _tables[i];
if (cur)
{
return ConstIterator(cur, this);
}
}
return End();
}
ConstIterator End() const
{
return ConstIterator(nullptr, this);
}
//质数表
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;
}
//构造函数
HashTable()
{
_tables.resize(__stl_next_prime(_tables.size()), nullptr);
}
//析构函数
~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;
}
}
//插入
pair<Iterator, bool> Insert(const T& data)
{
KeyOfT kot;
Iterator it = Find(kot(data));
//如果当前值已经存在
if (it != End())
{
//插入失败
return make_pair(it, false);
}
Hash hs;
//计算映射值
size_t hashi = hs(kot(data)) % _tables.size();
//如果负载因子为1,哈希表填满,需要扩容
if (_n == _tables.size())
{
//创建新的哈希表
vector<Node*> newtables(__stl_next_prime(_tables.size()), 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);
}
//头插
Node* newnode = new Node(data);
newnode->_next = _tables[hashi];
_tables[hashi] = newnode;
++_n;
return make_pair(Iterator(newnode, this), true);
}
Iterator Find(const K& key)
{
KeyOfT kot;
Hash hs;
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)
{
KeyOfT kot;
Hash hs;
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;
}
delete cur;
--_n;
return true;
}
prev = cur;
cur = cur->_next;
}
return false;
}
private:
vector<Node*> _tables; //指针数组
size_t _n = 0; //表中存储数据个数
};
}
UnorderedMap.h
cpp
#pragma once
#include "HashTable.h"
namespace bit
{
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);
}
V& operator[](const K& key)
{
pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));
return ret.first->second;
}
iterator Find(const K & key)
{
return _ht.Find(key);
}
bool Erase(const K & key)
{
return _ht.Erase(key);
}
private:
hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
};
}
UnorderedSet.h
cpp
#pragma once
#include "HashTable.h"
namespace bit
{
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;
};
}
测试代码
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include "HashTable.h"
#include "UnorderedSet.h"
#include "UnorderedMap.h"
void test_map()
{
bit::unordered_map<string, string> dict;
dict.insert({ "sort", "排序" });
dict.insert({ "left", "左边" });
dict.insert({ "right", "右边" });
dict["left"] = "左边,剩余";
dict["insert"] = "插入";
dict["string"];
bit::unordered_map<string, string>::iterator it = dict.begin();
while (it != dict.end())
{
// 不能修改first,可以修改second
//it->first += 'x';
it->second += 'x';
cout << it->first << ":" << it->second << endl;
++it;
}
cout << endl;
}
void test_set()
{
bit::unordered_set<int> s;
int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14, 3,3,15 };
for (auto e : a)
{
s.insert(e);
}
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
bit::unordered_set<int>::iterator it = s.begin();
while (it != s.end())
{
//不支持修改
//*it += 1;
cout << *it << " ";
++it;
}
cout << endl;
}
int main()
{
//test_map();
test_set();
return 0;
}