[C++-STL] 用哈希表封装unordered_map和unordered_set
unordered_map
和unordered_set
底层是由哈希表实现的, 那么本篇文章的内容 就是将哈希表封装为unordered_set
和 unordered_map
unordered_set
和unordered_map
使用, 所以在封装之前, 需要 改造哈希表
改造哈希表
温馨提示: 本篇文章底层哈希表的实现 使用 开散列法, 即 链地址法
阅读本文章之前, 建议先阅读博主 红黑树 和 set与map封装 的相关文章:
很相似
:-
与 set 和 map 一样, 在使用的时候, 第一个模板参数传Key数据的类型, 第二个模板参数传Value数据的类型(set没有这个模板参数), 但在
底层的结构中
, 第一个模板参数只是为了说明Key的类型,第二个模板参数才是真正的需要存储的数据类型
即 节点中存储的数据类型 一个是 Key, 另一个是 pair<Key, Value>
-
由于 set是单个类型作为数据类型, 而 map是两个数据类型组合成pair作为实际的数据类型, 所以想要使用一个相同的底层结构, 就要用到仿函数来针对不同的数据类型提取当前数据中的 key(因为需要用key实现许多操作)
即 与set和map原理相同的
KeyOfT仿函数
-
只要实现了可以同时兼容实现 set 与 map 的底层, 关于set 与 map 的接口实现就非常的简单
不同的是
, unordered_set 和 unordered_map 的底层是哈希表, 所以在哈希表中需要针对特别的数据类型(无法直接转换整形的类型)计算哈希值
, 所以 unordered_set 和 unordered_map 要多出一个 计算Key数据的哈希值的仿函数, 即 hashFunc
模板参数
template<class Key, class T, class KeyOfT, class hashFunc>
class hashTable {};
/* Key:关键字类型 T:存储的数据类型 KeyOfT:取数据中的关键字的仿函数 hashFunc:计算Key哈希值的仿函数*/
哈希表的迭代器 *
迭代器的模板参数应该是什么
.模板参数 成员变量
-
基本意义是需要
表示某一个数据节点的指针
, 所以 节点的数据类型需要知道, 即T
-
++操作, 迭代器根据哈希表中的桶的顺序及桶的内容 向后移动, 指向下一个节点
很明显, 想要实现在这个功能, 只知道某一个节点是不行的, 因为
哈希表存在多个桶(多个链表), 只知道某单个链表上的某个节点, 是不可能实现遍历整个哈希表的
. 所以每个迭代器中还需要知道迭代器指向节点所在的整个哈希表
, 也就是需要获取 此哈希表的指针那么 其实
迭代器的模板参数需要与哈希表的模板参数一模一样
数据节点的指针变量, 节点所在哈希表的指针变量
:template<class Key, class T, class KeyOfT, class hashFunc>
struct __hashTableIterator{
typedef hashNode<T> Node;
typedef hashTable<Key, T, KeyOfT, hashFunc> hashTable;
typedef __hashTableIterator<Key, T, KeyOfT, hashFunc> Self; // 迭代器本身类型 typedef为 Self
private:
Node* _node; // 节点指针
hashTable* _pht; // 节点所在哈希表的指针
};
成员函数
迭代器实际需要实现的功能太多了, 就不一一列举了, 只举几个比较重要的
构造函数
__hashTableIterator() {}
__hashTableIterator(Node* node, hashTable* pht)
:_node(node)
, _pht(pht)
{}
operator++
迭代器移向下一个节点
-
当迭代器指向的节点 是桶的非尾节点时
此时 可以直接 指向下一个节点, 因为桶还没有遍历完, 只需要像单链表那样向后移动就可以了
-
当迭代器指向的节点 是桶的尾节点时
此时 就要面临一个问题: 已经是当前桶的最后一个数据节点了, ++之后,
迭代器需要到下一个不为空的桶的头节点位置, 怎么寻找下一个桶?
根据节点的Key数据计算出当前节点所在的桶的位置
, 然后从此位置在哈希表里向后找第一个不为空的位置
就是下一个桶Self& operator++() {
if(_node->_next != nullptr) { // 当前节点的 _next不为空, 表示不是桶的尾节点
_node = _node->next;
}
else {
KeyOfT kot; // 创建取key值的仿函数对象
hashFunc hf; // 创建计算key的哈希值的仿函数对象
// 在使用时 仿函数可以直接使用匿名对象
size_t hashI = hf(kot(_node->_data)) % _pht->_tables.size(); // 先用kot调用取key值的仿函数, 再用hf调用计算key的哈希值的仿函数, 然后 % 哈希表数组的容量, 求出当前桶的哈希地址
// 向后查找 不为空的桶
hashI++;
for(; hashI < _pht->_tables.size(); hashI++) {
if(_pht->_tables[hashI]) { // 哈希表数组的 hashI 位置不为空, 表示此桶不为空
_node = _pht->_tables[hashI]; // 更新迭代器节点为此桶的头节点
break;
}
}
// 如果循环正常走完, 就表示没有找到下一个非空桶
// 也就表示 当前迭代器所指向的节点 是整个哈希表中的最后一个数据节点
// 那么 ++操作之后, 节点应该指向空
if(hashI == _pht->_tables.size()) {
_node = nullptr;
}
// 上面 存在直接访问哈希表私有变量的情况, 所以 迭代器类需要设置为哈希表类的友元函数
}
return *this;
}
operator* operator->
*的作用可以看作对结点指针(迭代器可以看成类似结点指针)的解引用
, 也就是说 *迭代器 可以直接访问 修改节点 数据
, 所以 :Node& operator*() {
return _node->_data;
}
->的作用, 则是访问节点数据的地址
:Node* operator->() {
return &_node->_data;
}
operator== opertor!=
bool operator==(const Self& sIt) const {
return _node == sIt._node;
}
bool operator!=(const Self& sIt) const {
return _node != sIt._node;
}
接口函数
了哈希表结构 与 接口函数
结构
template<class Key>
struct defaultHashFunc {
size_t operator()(const Key& key) {
return (size_t)key;
}
};
template<>
struct defaultHashFunc<string> {
size_t operator()(const string& str) {
size_t hash = 0;
for(auto c : str) {
hash = hash * 131 + c;
}
return hash;
}
};
template<class T>
struct hashNode{
T _data;
hashNode<T>* _next;
hashNode(const T& data)
:_data(data)
,_next(nullptr)
{}
};
template<class Key, class T, class KeyOfT, class hashFunc>
class hashTable {
typedef hashNode<T> Node;
template<class KeyType, class Type, class KeyOfType, class hashFunction>
friend struct __hashTableIterator;
public:
typedef __hashTableIterator<Key, T, KeyOfT, hashFunc> iterator;
private:
vector<Node*> _tables; // 存储桶的数组
size_t _n = 0; // 存储的节点数
};
hashTable
类内 声明__hashTableIterator
为友元类时, __hashTableIterator
生命的模板参数不可以与hashTable
的模板参数使用相同的命名, 即使 模板参数没有什么实际意义template<class Key, class T, class KeyOfT, class hashFunc>
class hashTable {
template<class KeyType, class Type, class KeyOfType, class hashFunction>
friend struct __hashTableIterator;
};
hashTable
内部声明__hashTableIterator
为友元hashTable
的模板参数为template<class Key, class T, class KeyOfT, class hashFunc>
__hashTableIterator
就不能template<class Key, class T, class KeyOfT, class hashFunc>
template<class Key, class T, class KeyOfT, class hashFunc>
class hashTable {
template<class Key, class T, class KeyOfT, class hashFunc>
friend struct __hashTableIterator;
};
g++
编译, 是会报错的MSVC
的编译器, 就不会出错接口函数
insert
- 返回值, insert 的返回值应该是 pair<iterator, bool> 类型的, 一个是插入数据的节点所在的迭代器, 另一个是插入结果
- 计算哈希值, 需要先用 KeyOfT仿函数取key值, 再进行计算
pair<iterator, bool> insert(const T& data) {
hashFunc hf;
KeyOfT kot;
// 先查找哈希表中是否已经存在此数据
iterator pos = find(kot(data));
if (pos != end()) { // 迭代器存在, 且 != end(), 即表示存在此数据
return make_pair(pos, false);
}
// 当 负载因子 == 1 时, 数组扩容
if(_tables.size() == _n) {
size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
vector<Node*> newTables;
newTables.resize(newSize, nullptr);
for(int i = 0; i < _tables.size(); i++) {
Node* cur = _tables[i];
while (cur) {
Node* next = cur->_next;
size_t hashI = hf(kot(cur->_data)) % newSize; // 取key值 以新数组的大小映射哈希地址
cur->_next = newTables[hashI];
newTables[hashI] = cur;
cur = next;
}
_tables[i] = nullptr;
}
// 新数组映射结束, 交换数组
_tables.swap(newTables);
}
// 扩容结束
// 计算 需要插入的哈希桶的位置
size_t hashI = hf(kot(data)) % _tables.size();
// 头插数据
Node* newNode = new Node(data);
newNode->_next = _tables[hashI];
_tables[hashI] = newNode;
++_n;
return make_pair(iterator(newNode, this), true);
}
find
iterator find(const Key& key) {
if(_n == 0) {
return iterator(nullptr, false);
}
hashFunc hf;
KeyOfT kot;
size_t hashI = hf(key) % _tables.size();
Node* cur = _tables[hashI];
while (cur) {
if (kot(cur->_data) == key) {
return iterator(cur, this);
}
cur = cur->_next;
}
// 走到这里就是没有找到
return iterator(nullptr, this);
}
erase
bool erase(const Key& key) {
hashFunc hf;
KeyOfT kot;
size_t hashI = hf(key) % _tables.size();
Node* cur = _tables[hashI];
Node* prev = nullptr;
while (cur) {
if (kot(cur->_data) == key) {
if (cur == _tables[hashI]) { // 如果需要删除的节点是桶的头节点
_tables[hashI] = cur->_next;
}
else {
prev->_next = cur->_next;
}
delete cur;
--_n;
return true;
}
prev = cur;
cur = cur->_next;
}
// 走到这里说明 没有可删除的节点
return false;
}
begin end
iterator begin() {
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);
}
析构函数
需要写析构函数将 桶 释放
~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;
}
}
封装 unordered_set 与 unordered_map
unordered_set
结构
哈希表 和 针对unordered_set取key值的仿函数
而已:template<class Key, class hashFunc = defaultHashFunc<Key>>
class unordered_set {
struct KeyOfT_set {
const Key& operator()(const Key& key) {
return key;
}
};
public:
typedef typename hashTable<Key, Key, KeyOfT_set, hashFunc>::iterator iterator;
private:
hashTable<Key, Key, KeyOfT_set, hashFunc> _ht;
};
对于这里的 hashFunc可能会有疑问: 为什么要在这里传 hashFunc?
因为Key的类型是在使用unordered_set时 传过去的, 那么针对Key值计算哈希值的仿函数 也应该在使用 unordered_set 时传参
同样也是因为,
unordered_set 是封装后的hashTable, 用户是不能直接修改 hashTable的, 所以需要在上层传参
接口函数
iterator begin() {
return _ht.begin();
}
iterator end() {
return _ht.end();
}
pair<iterator, bool> insert(const Key& key) {
return _ht.insert(key);
}
iterator find(const Key& key) {
return _ht.find(key);
}
bool erase(const Key& key) {
return _ht.erase(key);
}
unordered_map
结构
template<class Key, class Value, class hashFunc = defaultHashFunc<Key>>
class unordered_map {
struct KeyOfT_map {
const Key& operator()(const pair<Key, Value>& kv) {
return kv.first;
}
};
public:
typedef typename hashTable<Key, pair<Key, Value>, KeyOfT_map, hashFunc>::iterator iterator;
private:
hashTable<Key, pair<Key, Value>, KeyOfT_map, hashFunc> _ht;
};
接口函数
iterator begin() {
return _ht.begin();
}
iterator end() {
return _ht.end();
}
pair<iterator, bool> insert(const pair<Key, Value>& kv) {
return _ht.insert(kv);
}
iterator find(const Key& key) {
return _ht.find(key);
}
bool erase(const Key& key) {
return _ht.erase(key);
}
代码整理
哈希表
template<class Key>
struct defaultHashFunc {
size_t operator()(const Key& key) {
return (size_t)key;
}
};
template<>
struct defaultHashFunc<string> {
size_t operator()(const string& str) {
size_t hash = 0;
for(auto c : str) {
hash = hash * 131 + c;
}
return hash;
}
};
template<class T>
struct hashNode{
T _data;
hashNode<T>* _next;
hashNode(const T& data)
:_data(data)
,_next(nullptr)
{}
};
// 需要在 迭代器类前声明 哈希表类
template<class Key, class T, class KeyOfT, class hashFunc>
class hashTable;
// 迭代器
template<class Key, class T, class KeyOfT, class hashFunc>
struct __hashTableIterator{
typedef hashNode<T> Node;
typedef hashTable<Key, T, KeyOfT, hashFunc> hashTable;
typedef __hashTableIterator<Key, T, KeyOfT, hashFunc> Self; // 迭代器本身类型 typedef为 Self
public:
__hashTableIterator() {}
__hashTableIterator(Node* node, hashTable* pht)
:_node(node)
, _pht(pht)
{}
Self& operator++() {
if(_node->_next != nullptr) { // 当前节点的 _next不为空, 表示不是桶的尾节点
_node = _node->next;
}
else {
KeyOfT kot; // 创建取key值的仿函数对象
hashFunc hf; // 创建计算key的哈希值的仿函数对象
// 在使用时 仿函数可以直接使用匿名对象
size_t hashI = hf(kot(_node->_data)) % _pht->_tables.size(); // 先用kot调用取key值的仿函数, 再用hf调用计算key的哈希值的仿函数, 然后 % 哈希表数组的容量, 求出当前桶的哈希地址
// 向后查找 不为空的桶
hashI++;
for(; hashI < _pht->_tables.size(); hashI++) {
if(_pht->_tables[hashI]) { // 哈希表数组的 hashI 位置不为空, 表示此桶不为空
_node = _pht->_tables[hashI]; // 更新迭代器节点为此桶的头节点
break;
}
}
// 如果循环正常走完, 就表示没有找到下一个非空桶
// 也就表示 当前迭代器所指向的节点 是整个哈希表中的最后一个数据节点
// 那么 ++操作之后, 节点应该指向空
if(hashI == _pht->_tables.size()) {
_node = nullptr;
}
// 上面 存在直接访问哈希表私有变量的情况, 所以 迭代器类需要设置为哈希表类的友元函数
}
return *this;
}
Node& operator*() {
return _node->_data;
}
Node* operator->() {
return &_node->_data;
}
bool operator==(const Self& sIt) const {
return _node == sIt._node;
}
bool operator!=(const Self& sIt) const {
return _node != sIt._node;
}
private:
Node* _node; // 节点指针
hashTable* _pht; // 节点所在哈希表的指针
};
template<class Key, class T, class KeyOfT, class hashFunc>
class hashTable {
typedef hashNode<T> Node;
template<class Key, class T, class KeyOfT, class hashFunc>
friend struct __hashTableIterator;
public:
typedef __hashTableIterator<Key, T, KeyOfT, hashFunc> iterator;
~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) {
hashFunc hf;
KeyOfT kot;
// 先查找哈希表中是否已经存在此数据
iterator pos = find(kot(data));
if (pos != end()) { // 迭代器存在, 且 != end(), 即表示存在此数据
return make_pair(pos, false);
}
// 当 负载因子 == 1 时, 数组扩容
if(_tables.size() == _n) {
size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
vector<Node*> newTables;
newTables.resize(newSize, nullptr);
for(int i = 0; i < _tables.size(); i++) {
Node* cur = _tables[i];
while (cur) {
Node* next = cur->_next;
size_t hashI = hf(kot(cur->_data)) % newSize; // 取key值 以新数组的大小映射哈希地址
cur->_next = newTables[hashI];
newTables[hashI] = cur;
cur = next;
}
_tables[i] = nullptr;
}
// 新数组映射结束, 交换数组
_tables.swap(newTables);
}
// 扩容结束
// 计算 需要插入的哈希桶的位置
size_t hashI = hf(kot(data)) % _tables.size();
// 头插数据
Node* newNode = new Node(data);
newNode->_next = _tables[hashI];
_tables[hashI] = newNode;
++_n;
return make_pair(iterator(newNode, this), true);
}
// 查找数据
iterator find(const Key& key) {
if(_n == 0) {
return iterator(nullptr, this);
}
hashFunc hf;
KeyOfT kot;
size_t hashI = hf(key) % _tables.size();
Node* cur = _tables[hashI];
while (cur) {
if (kot(cur->_data) == key) {
return iterator(cur, this);
}
cur = cur->_next;
}
// 走到这里就是没有找到
return iterator(nullptr, this);
}
// 删除数据
bool erase(const Key& key) {
hashFunc hf;
KeyOfT kot;
size_t hashI = hf(key) % _tables.size();
Node* cur = _tables[hashI];
Node* prev = nullptr;
while (cur) {
if (kot(cur->_data) == key) {
if (cur == _tables[hashI]) { // 如果需要删除的节点是桶的头节点
_tables[hashI] = cur->_next;
}
else {
prev->_next = cur->_next;
}
delete cur;
--_n;
return true;
}
prev = cur;
cur = cur->_next;
}
// 走到这里说明 没有可删除的节点
return false;
}
iterator begin() {
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);
}
private:
vector<Node*> _tables; // 存储桶的数组
size_t _n = 0; // 存储的节点数
};
unordered_set
template<class Key, class hashFunc = defaultHashFunc<Key>>
class unordered_set {
struct KeyOfT_set {
const Key& operator()(const Key& key) {
return key;
}
};
public:
typedef typename hashTable<Key, Key, KeyOfT_set, hashFunc>::iterator iterator;
iterator begin() {
return _ht.begin();
}
iterator end() {
return _ht.end();
}
pair<iterator, bool> insert(const Key& key) {
return _ht.insert(key);
}
iterator find(const Key& key) {
return _ht.find(key);
}
bool erase(const Key& key) {
return _ht.erase(key);
}
private:
hashTable<Key, Key, KeyOfT_set, hashFunc> _ht;
};
unordered_map
template<class Key, class Value, class hashFunc = defaultHashFunc<Key>>
class unordered_map {
struct KeyOfT_map {
const Key& operator()(const pair<Key, Value>& kv) {
return kv.first;
}
};
public:
typedef typename hashTable<Key, pair<Key, Value>, KeyOfT_map, hashFunc>::iterator iterator;
iterator begin() {
return _ht.begin();
}
iterator end() {
return _ht.end();
}
pair<iterator, bool> insert(const pair<Key, Value>& kv) {
return _ht.insert(kv);
}
iterator find(const Key& key) {
return _ht.find(key);
}
bool erase(const Key& key) {
return _ht.erase(key);
}
private:
hashTable<Key, pair<Key, Value>, KeyOfT_map, hashFunc> _ht;
};
作者: 哈米d1ch 发表日期:2022 年 11 月 13 日