1. 闭散式
仿函数
struct DefaultHashFunc
{
size_t operator()(const K& key)
{
return (size_t)key;
}
};
template<>
struct DefaultHashFunc<string>//模板的特化
{
size_t operator()(const string& str)
{
size_t hash = 0;
for (auto ch : str)
{
hash *= 131;//字符串哈希算法
hash += ch;
}
return hash;
}
};
因为我们并不知道存储的是字符串还是整数,所以我们需要对字符串进行处理,运用字符串哈希算法(可以减少冲突概率),转化为整数再进行后续的操作。
1.1 存储节点结构的定义
enum STATE
{
EXIST,
EMPTY,
DELETE
};
template<class K, class V>
struct HashData
{
std::pair<K, V> _kv;
STATE _state = EMPTY;
};
用一个结构体来表示当前的存储状态,存在,空,还是删除过的。
1.2 存储结构的定义
std::vector<HashData<K, V>> _table;
size_t _n = 0;//记录有效值的个数
使用vector数组包含一个个HashData对象,_n来记录有效个数的值,为后续扩容做准备。
1.3 构造函数
HashTable()
{
_table.resize(10);
}
vector是内置类型,会调用默认的析构函数,HashData会去调用它自己的析构函数,HashData的成员爷没有申请多态资源,也不需要手动释放,交给编译器即可,所以也就不需要写析构函数。
1.4 insert插入函数
bool insert(const std::pair<K, V>& kv)
{
if (find(kv.first))
{
return false;
}
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;
}
先进行判断,插入的元素是否已经存在与数组中,避免数据冗余,判断是否已经超过负载因子的限定,超过了就需要进行扩容操作,遍历旧表,把已经存在的值重新插入一遍。
利用线性探测的方式,先计算出下标,判断是否已经被占用,被占用旧往下遍历,找到没有被占用的值,进行插入,有效数据个数++。
ps:当扩容后,数组的大小已经改变了,所以就应当需要进行重新映射,重新映射也有效数据个数是不变的,也不需要更新有效数据个数,同时对于数据聚集也可以有更好的缓解。
ps:如果想要降低数据的聚集问题,可以使用二次探测。
1.5 find查找函数
HashData<const K, V>* find(const K& key) //8(exist) (delete) 6,在8的位置后面找不会因为delete找不到6
{
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];//有一个隐式类型转化,权限的缩小,K->const K
}
++hashi;
hashi %= _table.size();
}
return nullptr;
}
先计算下标,如果计算出来的下标不是为空的话,就需要进一步判断是被删除了还是存在的,如果当前下标的状态是存在并且存储的值也相符合的话,就返回当前下标的类指针。
1.6 erase删除函数
bool erase(const K& key)
{
HashData<const K, V>* ret = find(key);
if (ret)
{
ret->_state = DELETE;
--_n;
return true;
}
return false;
}
复用find函数,找到了就直接把状态设为删除,有效数据个数--。
完整模拟实现
#pragma once
#include<vector>
#include <string>
#include <map>
template<class K>
struct DefaultHashFunc
{
size_t operator()(const K& key)
{
return (size_t)key;
}
};
template<>
struct DefaultHashFunc<string>//模板的特化
{
size_t operator()(const string& str)
{
size_t hash = 0;
for (auto ch : str)
{
hash *= 131;//字符串哈希算法
hash += ch;
}
return hash;
}
};
namespace open_address
{
enum STATE
{
EXIST,
EMPTY,
DELETE
};
template<class K, class V>
struct HashData
{
std::pair<K, V> _kv;
STATE _state = EMPTY;
};
template<class K, class V, class HashFunc = DefaultHashFunc<K>>//加一个仿函数来分别解决整型和string的find函数的%问题。
class HashTable
{
public:
HashTable()
{
_table.resize(10);
}
bool insert(const std::pair<K, V>& kv)
{
if (find(kv.first))
{
return false;
}
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) //8(exist) (delete) 6,在8的位置后面找不会因为delete找不到6
{
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];//有一个隐式类型转化,权限的缩小,K->const K
}
++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:
std::vector<HashData<K, V>> _table;
size_t _n = 0;//记录有效值的个数
};
}
2. 开散式
同样开散式也会面临着不知道存储的是字符串还是整数,为了处理计算映射位置的问题,也需要仿函数。
哈希表(开散式)又称为哈希桶。

存储的值被一个个的挂在下面,所以又称哈希桶
2.1 存储节点结构的定义
template<class T>
struct HashNode
{
T _data;
HashNode* _next;
HashNode(const T&data)
:_data(data)
,_next(nullptr)
{}
};
哈希表需要维护一个单链表,需要把一个个的节点串起来,所以需要一个指针来指向下一个节点。
2.2 存储结构的定义
typedef HashNode<T> Node;
vector<Node*> _table;
size_t _n = 0;//存储有效数字个数
因此数组里面的存储类型也就变成了节点指针。
2.3 构造函数和析构函数
HashTable()
{
_table.resize(10, nullptr);
}
~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;
}
}
因为是单链表,有申请动态空间,所以在析构时,需要手动遍历其中的所有节点,将其释放,避免内存泄漏。
2.4 迭代器
//前置声明
template<class K, class T, class keyofT, class HashFunc > class HashTable;//因为迭代器中需要用到哈希表
//HTiterator<K, T, const T*, const T&, keyofT, HashFunc>
template<class K, class T,class Ref,class Ptr, class keyofT, class HashFunc = DefaultHashFunc<K>>
struct HTiterator
{
typedef HashNode<T> Node;
typedef HTiterator<K, T, Ref, Ptr, keyofT, HashFunc> self;
typedef HTiterator<K, T, T*, T&, keyofT, HashFunc> iterator;
Node* _node;
const 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)
{}
Ptr operator*()
{
return _node->_data;
}
Ref operator->()
{
return &_node->_data;
}
self& operator++()
{
if (_node->_next)
{
_node = _node->_next;
}
else
{
HashFunc hf;
keyofT kot;
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;
}
};
迭代器部分重点介绍迭代器的移动,如果当前节点的_next指针不为空,就直接移动到下一个节点的指针,如果为空,就需要去寻找下一个不为空的节点的指针,先计算出当前节点的下标,在保证不越界的情况下,判断当前节点的指针是否为空,为空,就++下标。
2.5 迭代器和存储结构类的注意
因为迭代器中需要使用到HashTable,在HashTable里面也需要使用到迭代器,编译器是会向上去寻找的,在迭代器中会找不到HashTable,所以可以在前面做一个前置声明,又来一个新的问题就是迭代器里面使用不了HashTable的私有成员,在HashTable把迭代器设为友员,你想要使用我,就把你变成我的朋友,还要一个注意的点,就是在前置声明和设置友员的时候,是不可以在加缺省值的。

因为迭代器和HashTable也就设置了HashFunc的缺省值,无论在前置声明还是在友员的设置中就不能对HashFunc在设置缺省值了。
![]()
不然就会出现重定义默认参数,就算设置默认参数一样也不可以。
2.6 迭代器的begin和end实现(包含const)
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);
}
const_iterator begin()const
{
for (size_t i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
if (cur)
{
return const_iterator(cur, this);
}
}
return const_iterator(nullptr, this);
}
const_iterator end()const
{
return const_iterator(nullptr, this);
}
找begin也就是找第一个不为空的桶,遍历数组,如果不为空就返回。
2.7 insert插入函数
pair<iterator,bool> insert(const T& data)
{
keyofT kot;
HashFunc hf;
iterator it = find(kot(data));
if (it != end())
{
return make_pair(it, false);
}
if (_n == _table.size())//扩容,也就是负载因子为1的时候,相对于每个桶挂一个
{
size_t newsize = _table.size() * 2;
vector<Node*> newTable;
newTable.resize(newsize, nullptr);//这里的扩容没有像开放定址法一样去复用insert因为复用了就需要重新开节点的空间
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);
}
在正常插入时,复用insert函数,没有找到就创建,我们使用的头插,因为不用在意排序的问题,头尾插都是可以的。
扩容问题:这边负载因子设为1,也就是最好的情况下一个节点挂一个,这边遍历找到不为空的桶,但是不需要再像闭散式一样去复用insert,不然就需要重新开空间,重新插入,这边的实现方法也很优雅,遍历不为空的桶,重新计算映射规则,把不为空的桶里面的节点顺手牵羊,直接挂接到新表中即可。
2.8 find查找函数
iterator 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();
}
计算下标,再当前桶去遍历,如果找到了就返回当前节点的迭代器,如果没有就返回nullptr。
2.9 eraser删除函数
bool erase(const K& key)
{
HashFunc hf;
keyofT kot;
size_t hashi = hf(key) % _table.size();
Node* cur = _table[hashi];
Node* prev = nullptr;
while (cur)
{
if (kot(cur->_data) == key)
{
if (prev == nullptr)
{
_table[hashi] = cur->_next;
}
else
{
prev->_next = cur->_next;
}
delete cur;
return true;
}
prev = cur;
cur = cur->_next;
}
return false;
}
因为是单链表,只有指向下一个节点的指针,所以我们还需要记录上一个节点,当找到了要删除的节点,要建立要删除节点的上一个节点和下一个节点的联系。
完整模拟实现
#pragma once
#include<vector>
#include <string>
#include <map>
template<class K>
struct DefaultHashFunc
{
size_t operator()(const K& key)
{
return (size_t)key;
}
};
template<>
struct DefaultHashFunc<string>//模板的特化
{
size_t operator()(const string& str)
{
size_t hash = 0;
for (auto ch : str)
{
hash *= 131;//字符串哈希算法
hash += ch;
}
return hash;
}
};
namespace hash_bucket//哈希桶
{
template<class T>
struct HashNode
{
T _data;
HashNode* _next;
HashNode(const T&data)
:_data(data)
,_next(nullptr)
{}
};
//前置声明
template<class K, class T, class keyofT, class HashFunc > class HashTable;//因为迭代器中需要用到哈希表
//HTiterator<K, T, const T*, const T&, keyofT, HashFunc>
template<class K, class T,class Ref,class Ptr, class keyofT, class HashFunc = DefaultHashFunc<K>>
struct HTiterator
{
typedef HashNode<T> Node;
typedef HTiterator<K, T, Ref, Ptr, keyofT, HashFunc> self;
typedef HTiterator<K, T, T*, T&, keyofT, HashFunc> iterator;
Node* _node;
const 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)
{}
Ptr operator*()
{
return _node->_data;
}
Ref operator->()
{
return &_node->_data;
}
self& operator++()
{
if (_node->_next)
{
_node = _node->_next;
}
else
{
HashFunc hf;
keyofT kot;
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;
}
};
template<class K,class T,class keyofT,class HashFunc= DefaultHashFunc<K>>
class HashTable
{
typedef HashNode<T> Node;
public:
typedef HTiterator<K, T, T*, T&, keyofT, HashFunc> iterator;
typedef HTiterator<K, T, const T*, const T&, keyofT, HashFunc> const_iterator;
//友员声明
template<class K, class T, class Ref,class Ptr,class keyofT, class HashFunc > friend struct HTiterator;
HashTable()
{
_table.resize(10, nullptr);
}
~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;
}
}
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);
}
const_iterator begin()const
{
for (size_t i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
if (cur)
{
return const_iterator(cur, this);
}
}
return const_iterator(nullptr, this);
}
const_iterator end()const
{
return const_iterator(nullptr, this);
}
pair<iterator,bool> insert(const T& data)
{
keyofT kot;
HashFunc hf;
iterator it = find(kot(data));
if (it != end())
{
return make_pair(it, false);
}
if (_n == _table.size())//扩容,也就是负载因子为1的时候,相对于每个桶挂一个
{
size_t newsize = _table.size() * 2;
vector<Node*> newTable;
newTable.resize(newsize, nullptr);//这里的扩容没有像开放定址法一样去复用insert因为复用了就需要重新开节点的空间
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);
}
iterator 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* cur = _table[hashi];
Node* prev = nullptr;
while (cur)
{
if (kot(cur->_data) == key)
{
if (prev == nullptr)
{
_table[hashi] = cur->_next;
}
else
{
prev->_next = cur->_next;
}
delete cur;
return true;
}
prev = cur;
cur = cur->_next;
}
return false;
}
private:
vector<Node*> _table;
size_t _n = 0;//存储有效数字个数
};
}
unorder_map
#pragma once
#include"hashtable.h"
#include <map>
namespace bit
{
template<class K, class V>
class unordered_map
{
struct mapkeyofT
{
const K& operator()(const std::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);
}
bool erase(const K& kv)
{
return _ht.erase(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;
};
}
unordered_set
#pragma once
#include"hashtable.h"
namespace bit
{
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;
const_iterator begin() const
{
return _ht.begin();
}
const_iterator end() const
{
return _ht.end();
}
pair<iterator,bool> insert(const K& key)//这里的iterator是const iterator,hashtable里面的insert的是普通的迭代器(注意,这里是类型不匹配,跟权限的放大缩小没关系)
{
return _ht.insert(key);
}
private:
hash_bucket::HashTable<K, K, setkeyofT> _ht;
};
}
这里的unordered_map和unordered_set几乎没有什么区别就不做讲解了。
1071

被折叠的 条评论
为什么被折叠?



