您现在的位置是:首页 >技术交流 >哈希表、unordered_map和unordered_set模拟网站首页技术交流
哈希表、unordered_map和unordered_set模拟
目录
哈希表
哈希概念:通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。
ps:该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(Hash Table)(或者称散列表)
插入元素:根据待插入的元素的关键码,以此函数计算出该元素的存放位置并存放。
搜索元素:根据元素的关键码,计算出该元素的存放位置,在该位置取元素比较,如果关键码相等,则查找成功。
哈希冲突:不同关键字通过相同哈希函数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。
常见哈希函数----->1. 直接定址法--(常用)2. 除留余数法--(常用)
解决哈希冲突的常见两种方法:闭散列和开散列
闭散列
闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有 空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。
线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。
//线性探测
size_t start = val.first % _ht.size();
size_t i = 0;
size_t index = start;
while (_ht[index]._state == EXIST) {
i++;
index = start + i;
index %= _ht.size();
}
插入
1、通过哈希函数获取待插入元素在哈希表中的位置
2、如果该位置没有元素则直接插入新元素,如果该位置中发生哈希冲突,则使用线性探测找到下一个空位置,插入新元素。
删除
采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索(ps:因为对于其他元素的查找遇见EMPTY即停止查找)。因此,线性探测采用标记来删除一个元素。
闭散列实现如下:
template<class K>//仿函数处理key
struct Hash {
size_t operator()(const K& key) {
return key;
}
};
template<>
struct Hash<string> {
size_t operator()(const string& s) {
size_t value = 0;
for (auto e : s) {
value *= 31;
value += e;
}
return value;
}
};
namespace closeHash {
enum State {
EXIST,
DELETE,
EMPTY
};
template<class K,class V>
struct HashData {
pair<K, V> _kv;
State _state = EMPTY;
};
template<class K,class V,class HashFunc = Hash<K>>
class HashTable {
public:
bool Erase(const K& key) {
HashData<K, V>* ret = Find(key);
if (ret == nullptr)
return false;
else {
_n--;
ret->_state = DELETE;
return true;
}
}
HashData<K, V>* Find(const K& key) {
if (_table.size() == 0)
return nullptr;
HashFunc hf;
size_t start = hf(key) % _table.size();
size_t i = 0;
size_t index = start;
while (_table[index]._state != EMPTY) {
if (_table[index]._kv.first == key && _table[index]._state == EXIST)
return &_table[index];
i++;
index = start + i;
index %= _table.size();
}
return nullptr;
}
bool Insert(const pair<K, V>& kv) {
HashData<K, V>* ret = Find(kv.first);
if (ret)
return false;
if (_table.size() == 0 || _n * 10 / _table.size() > 7) {
//扩容
size_t newSize = _table.size() == 0 ? 10 : _table.size() * 2;
HashTable<K, V> newTable;
newTable._table.resize(newSize);
for (size_t i = 0; i < _table.size(); i++) {
if (_table[i]._state == EXIST)
newTable.Insert(_table[i]._kv);
}
_table.swap(newTable._table);
}
HashFunc hf;
size_t start = hf(kv.first) % _table.size();
size_t i = 0;
size_t index = start;
while (_table[index]._state != EMPTY) {
i++;
index = start + i;
index %= _table.size();
}
_table[index]._kv = kv;
_table[index]._state = EXIST;
_n++;
return true;
}
private:
vector<HashData<K, V>> _table;
size_t _n;
};
void test() {
HashTable<int, int> ht;
int a[] = { 2,12,22,32,42,52,62 };
for (auto& e : a) {
ht.Insert(make_pair(e, e));
}
ht.Insert(make_pair(72, 72));
ht.Insert(make_pair(32, 32));
//ht.Insert(make_pair(-1, -1));
ht.Insert(make_pair(-999, -999));
cout << ht.Find(12) << endl;
ht.Erase(12);
cout << ht.Find(12) << endl;
}
}
开散列
开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。
开散列的增容:桶的个数是一定的,随着元素的不断插入,每个桶中元素的个数不断增多,极端情况下,可 能会导致一个桶中链表节点非常多,会影响的哈希表的性能,因此在一定条件下需要对哈希 表进行增容,那该条件怎么确认呢?开散列最好的情况是:每个哈希桶中刚好挂一个节点, 再继续插入元素时,每一次都会发生哈希冲突,因此,在元素个数刚好等于桶的个数时,可以给哈希表增容。
size_t newSize = _table.size() == 0 ? 10 : _table.size() * 2;
开散列实现如下:
template<class K>//仿函数处理key
struct Hash {
size_t operator()(const K& key) {
return key;
}
};
template<>
struct Hash<string> {
size_t operator()(const string& s) {
size_t value = 0;
for (auto e : s) {
value *= 31;
value += e;
}
return value;
}
};
namespace openHash {
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 HashFunc = Hash<V>>
class HashBucket {
typedef HashNode<K, V> Node;
public:
bool Erase(const K& key) {
if (_table.empty())
return false;
HashFunc hf;
size_t index = hf(key) % _table.size();
Node* cur = _table[index];
Node* prev = nullptr;
while (cur) {
if (cur->_kv.first == key) {
if (prev == nullptr)
_table[index] = cur->_next;
else
prev->_next = cur->_next;
delete cur;
_n--;
return true;
}
prev = cur;
cur = cur->_next;
}
return false;
}
Node* Find(const K& key) {
if (_table.empty())
return nullptr;
HashFunc hf;
size_t index = hf(key) % _table.size();
Node* cur = _table[index];
while (cur) {
if (cur->_kv.first == key)
return cur;
cur = cur->_next;
}
return nullptr;
}
bool Insert(const pair<K, V>& kv) {
Node* ret = Find(kv.first);
if (ret)
return false;
HashFunc hf;
if (_n == _table.size()) {
//扩容
size_t newSize = _table.size() == 0 ? 10 : _table.size() * 2;
vector<Node*> newBucket;
newBucket.resize(newSize);
for (size_t i = 0; i < _table.size(); i++) {
Node* cur = _table[i];
while (cur) {
Node* next = cur->_next;
size_t index = hf(cur->_kv.first) % newBucket.size();
cur->_next = newBucket[index];
newBucket[index] = cur;
cur = next;
}
_table[i] = nullptr;
}
_table.swap(newBucket);
}
size_t index = hf(kv.first) % _table.size();
Node* newNode = new Node(kv);
newNode->_next = _table[index];
_table[index] = newNode;
_n++;
return true;
}
private:
vector<Node*> _table;
size_t _n;
};
void test() {
int a[] = { 4,24,14,7,37,27,57,67,34,14,54 };
HashBucket<int, int> ht;
for (auto e : a) {
ht.Insert(make_pair(e, e));
}
ht.Insert(make_pair(84, 84));
}
}
unordered_map和unordered_set模拟
对开散列的哈希表改造
1、将hashNode中存放的数据类型改为泛型
template<class T>
struct HashNode {
T _data;
HashNode<T>* _next;
HashNode(const T& data)
:_data(data)
,_next(nullptr)
{}
};
2、对哈希表增加迭代器
typedef __htIterator<K, T, T&, T*, KeyOfT, HashFunc> iterator;
iterator begin() {
for (size_t i = 0; i < _table.size(); i++) {
if (_table[i])
return iterator(_table[i], this);
}
return end();
}
iterator end() {
return iterator(nullptr, this);
}
具体增加的迭代器类如下:
template<class K,class T,class Ref, class Ptr,class KeyOfT,class HashFunc>
struct __htIterator {
typedef HashNode<T> Node;
typedef __htIterator<K, T, Ref, Ptr, KeyOfT, HashFunc> self;
//迭代器的成员变量
Node* _node;
HashBucket<K, T, KeyOfT, HashFunc>* _pht;
__htIterator(Node* node,HashBucket<K,T,KeyOfT,HashFunc>* pht)
:_node(node)
,_pht(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 index = hf(kot(_node->_data)) % _pht->_table.size();
index++;
//找到下一个不为空的桶
while (index < _pht->_table.size()) {
if (_pht->_table[index])
break;
else
index++;
}
if (index == _pht->_table.size())
_node = nullptr;
else
_node = _pht->_table[index];
}
return *this;
}
bool operator==(const self& s) const {
return _node == s._node;
}
bool operator!=(const self& s) const {
return _node != s._node;
}
};
3、对insert的返回值进行修改,改为pair<iterator,bool>,方便后续的unordered_map实现operator[]的实现。
pair<iterator,bool> Insert(const T& data)
4、对闭散列增加默认构造、拷贝函数、赋值和以及析构函数
HashBucket() = default;
HashBucket(const self& hb) {
_table.resize(hb._table.size());
for (size_t i = 0; i < hb._table.size(); i++) {
Node* cur = hb._table[i];
while (cur) {
Node* copy = new Node(cur->_data);
copy->_next = _table[i];
_table[i] = copy;
cur = cur->_next;
}
}
}
self& operator=(self hb) {
swap(_n, hb._n);
_table.swap(hb._table);
return *this;
}
~HashBucket() {
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;
}
}
完整的改造代码如下:
namespace openHash {
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 HashFunc>
class HashBucket;
template<class K,class T,class Ref, class Ptr,class KeyOfT,class HashFunc>
struct __htIterator {
typedef HashNode<T> Node;
typedef __htIterator<K, T, Ref, Ptr, KeyOfT, HashFunc> self;
Node* _node;
HashBucket<K, T, KeyOfT, HashFunc>* _pht;
__htIterator(Node* node,HashBucket<K,T,KeyOfT,HashFunc>* pht)
:_node(node)
,_pht(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 index = hf(kot(_node->_data)) % _pht->_table.size();
index++;
//找到下一个不为空的桶
while (index < _pht->_table.size()) {
if (_pht->_table[index])
break;
else
index++;
}
if (index == _pht->_table.size())
_node = nullptr;
else
_node = _pht->_table[index];
}
return *this;
}
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 HashFunc>
class HashBucket {
typedef HashNode<T> Node;
template<class K, class T, class Ref, class Ptr, class KeyOfT, class HashFunc>
friend struct __htIterator;
typedef HashBucket<K, T, KeyOfT, HashFunc> self;
public:
typedef __htIterator<K, T, T&, T*, KeyOfT, HashFunc> iterator;
/*HashBucket() {
}*/
HashBucket() = default;
HashBucket(const self& hb) {
_table.resize(hb._table.size());
for (size_t i = 0; i < hb._table.size(); i++) {
Node* cur = hb._table[i];
while (cur) {
Node* copy = new Node(cur->_data);
copy->_next = _table[i];
_table[i] = copy;
cur = cur->_next;
}
}
}
self& operator=(self hb) {
swap(_n, hb._n);
_table.swap(hb._table);
return *this;
}
~HashBucket() {
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++) {
if (_table[i])
return iterator(_table[i], this);
}
return end();
}
iterator end() {
return iterator(nullptr, this);
}
bool Erase(const K& key) {
if (_table.empty())
return false;
HashFunc hf;
size_t index = hf(key) % _table.size();
Node* cur = _table[index];
Node* prev = nullptr;
KeyOfT kot;
while (cur) {
if (kot(cur->_data) == key) {
if (prev == nullptr)
_table[index] = cur->_next;
else
prev->_next = cur->_next;
delete cur;
_n--;
return true;
}
prev = cur;
cur = cur->_next;
}
return false;
}
iterator Find(const K& key) {
if (_table.empty())
return end();
HashFunc hf;
size_t index = hf(key) % _table.size();
Node* cur = _table[index];
KeyOfT kot;
while (cur) {
if (kot(cur->_data) == key)
return iterator(cur, this);
cur = cur->_next;
}
return end();
}
pair<iterator,bool> Insert(const T& data) {
KeyOfT kot;
iterator ret = Find(kot(data));
if (ret != end())
return make_pair(ret, false);
HashFunc hf;
if (_n == _table.size()) {
//扩容
size_t newSize = _table.size() == 0 ? 10 : _table.size() * 2;
vector<Node*> newBucket;
newBucket.resize(newSize);
for (size_t i = 0; i < _table.size(); i++) {
Node* cur = _table[i];
while (cur) {
Node* next = cur->_next;
size_t index = hf(kot(cur->_data)) % newBucket.size();
cur->_next = newBucket[index];
newBucket[index] = cur;
cur = next;
}
_table[i] = nullptr;
}
_table.swap(newBucket);
}
size_t index = hf(kot(data)) % _table.size();
Node* newNode = new Node(data);
newNode->_next = _table[index];
_table[index] = newNode;
_n++;
return make_pair(iterator(newNode, this), true);
}
private:
vector<Node*> _table;
size_t _n = 0;
};
}
unordered_set模拟
unordered_set的底层使用的就是我们改造的开散列哈希表,因为改造的时候将hashnode中存放的数据改为了泛型,所以需要我们传入一个仿函数(这里我们传入的是SetKeyOfT这个函数),让哈希表拿到unordered_set的key,进行后续的逻辑操作。
除了SetKeyOfT这个仿函数,我们还需要另一个仿函数,即hash = Hash<K>,这个仿函数的作用是当我们通过SetKeyOfT拿到hashnode中的数据中的key值之后,将key的值转换成能够取模的整形。
#pragma once
#include "HTable.h"
namespace hzp {
template<class K, class hash = Hash<K>>
class My_Unordered_Set {
struct SetKeyOfT {
const K& operator()(const K& key) {
return key;
}
};
public:
typedef typename openHash::HashBucket<K, K, SetKeyOfT, hash>::iterator iterator;
iterator begin() {
return _hb.begin();
}
iterator end() {
return _hb.end();
}
pair<iterator, bool> insert(const K& key) {
return _hb.Insert(key);
}
private:
openHash::HashBucket<K, K, SetKeyOfT, hash> _hb;
};
void test_set() {
My_Unordered_Set<int> mus;
int a[] = { 4,14,34,7,24,17 };
for (auto e : a)
mus.insert(e);
My_Unordered_Set<int>::iterator it = mus.begin();
while (it != mus.end()) {
cout << *it << " ";
++it;
}
cout << endl;
My_Unordered_Set<string> uss;
uss.insert("sort");
uss.insert("hash");
}
}
unordered_map模拟
unordered_map的模拟与unordered_set的模拟极为相似,底层也是使用的改造的开散列的哈希表。请参考unordered_set的模拟读下边的代码。
#pragma once
#include "HTable.h"
namespace hzp {
template<class K, class V, class hash = Hash<K>>
class My_Unordered_Map {
struct MapKeyOfT {
const K& operator()(const pair<K,V>& key) {
return key.first;
}
};
public:
typedef typename openHash::HashBucket<K, pair<K, V>, MapKeyOfT, hash>::iterator iterator;
iterator begin() {
return hb.begin();
}
iterator end() {
return hb.end();
}
V& operator[](const K& key) {
auto ret = hb.Insert(make_pair(key, V()));
return ret.first->second;
}
pair<iterator,bool> insert(const pair<K, V>& kv) {
return hb.Insert(kv);
}
private:
openHash::HashBucket<K, pair<K, V>, MapKeyOfT, hash> hb;
};
void test_map() {
My_Unordered_Map<string, string> dict;
dict.insert(make_pair("sort", "排序"));
dict.insert(make_pair("string", "ַ字符串"));
dict.insert(make_pair("map", "地图"));
dict["sort"] = "排序1";
cout << dict["sort"] << endl;
My_Unordered_Map<string, string>::iterator it = dict.begin();
while (it != dict.end())
{
cout << it->first << ":" << it->second << endl;
++it;
}
cout << endl;
My_Unordered_Map<string, string> copy(dict);
for (auto& e : copy) {
cout << e.first << ":" << e.second << endl;
}
}
}