设计容器时,哈希注重的是如何处理哈希冲突,显然,开散列的处理方式要优于闭散列,因此使用开散列处理哈希冲突,模拟实现的本质是对迭代器如何处理,因为其他的接口都是很好完成的,但是迭代器如何形成遍历的方式呢?
在封装迭代器前,我们先思考一个问题,如何进行类型的不同调整,因为底层是用除留余数法进行映射的,但是除了整形,还有什么能取余呢,因此需要对不同的类型进行不同的转换,我们只是模拟实现,因此处理最多使用的string类,使用模板的特化,对string类进行特殊处理,这样将其转换成对应的整形,这里也要使用一下仿函数来进行特化的处理。
迭代器的封装一样,封装的是节点,所以遍历的方式是和链表一样,但是最主要的问题是,一个桶遍历完了后,如何切换到下一个桶~
我们给出的方案是,将哈希表也封装在迭代器中,然后使用哈希表来定位桶。但这样也无疑使结构变得复杂,因此c++11中使用的方式是,将节点增加两个指针,一个是前置,一个后置,让插入顺序为这个链表的顺序,因此同样的节点有着两个顺序的链接方式,依此来处理迭代器,但是这样在插入和删除方向要复杂一些。
当然任何的事物都有其优缺点,但是在实现的方向上,我们主要学习其实现的思想,更多的是这种高维度的泛型技术,使用一个哈希表,同时满足两种实现,高维度的泛型技术是必修课。对于迭代器的处理我们可以想不出来,但是对于泛型的高维度使用却是必须掌握的。
#pragma once
#include <vector>
#include <string>
using namespace std;
template <class T>
struct HashNode
{
T _data;
HashNode<T>* _next;
HashNode(const T& data)
:_data(data)
,_next(nullptr)
{}
};
template<class K, class T, class KOfT, class Hash>
class HashTable;
template <class K,class T,class KOfT,class Hash>
struct __Hashiterator
{
typedef __Hashiterator<K, T, KOfT, Hash> Self;
typedef HashTable<K, T, KOfT, Hash> HT;
typedef HashNode<T> Node;
Node* _node;
HT* _pht;
__Hashiterator(Node* node,HT* pht)
:_node(node)
,_pht(pht)
{}
T& operator* ()
{
return _node->_data;
}
T* operator->()
{
return &_node->_data;
}
bool operator!=(Node* node)
{
return _node != node;
}
bool operator==(Node* node)
{
return _node == node;
}
Self operator++()
{
if (_node->_next)
{
_node = _node->_next;
}
else
{
KOfT koft;
size_t index = _pht->HashFunc(koft(_node->_data)) %
_pht->_tables.size();
index++;
while (index < _pht->_tables.size())
{
if (_pht->_tables[index])
{
_node = _pht->_tables[index];
return *this;
}
}
_node = nullptr;
}
return *this;
}
};
template <class K>
struct _Hash
{
const K& operator() (const K& key)
{
return key;
}
};
template <>
struct _Hash <string>
{
size_t operator() (const string& s)
{
size_t hash = 0;
for (int i = 0; i < s.size(); i++)
{
hash *= 131;
hash += s[i];
}
}
};
template<class K, class T, class KOfT ,class Hash>
class HashTable
{
friend struct __Hashiterator<K, T, KOfT, Hash>;
typedef HashNode<T> Node;
typedef __Hashiterator<K, T, KOfT, Hash> iterator;
public:
iterator begin()
{
for (int i = 0; i < _tables.size(); i++)
{
if (_tables[i])
{
return iterator(_tables[i], this);
}
}
return end();
}
iterator end()
{
return iterator(nullptr, this);
}
~HashTable()
{
clear();
}
void clear()
{
for (int i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
while (cur)
{
Node* next = cur->_next;
delete cur;
cur = next;
}
_tables[i] = nullptr;
}
}
size_t HashFunc(const K& key)
{
Hash hash;
return hash(key);
}
size_t GetPrime(size_t num)
{
const int PRIMECOUNT = 28;
const size_t primeList[PRIMECOUNT] =
{
53ul, 97ul, 193ul, 389ul, 769ul,
1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
1610612741ul, 3221225473ul, 4294967291ul
};
for (size_t i = 0; i < PRIMECOUNT; i++)
{
if (primeList[i] > num)
{
return primeList[i];
}
}
return primeList[28];
}
pair<iterator,bool> insert(const T& d)
{
KOfT koft;
if (_num == _tables.size())
{
vector<Node*> newtables;
size_t newsize = GetPrime(_tables.size());
//size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
newtables.resize(newsize);
for (int i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
while (cur)
{
Node* next = cur->_next;
int index = HashFunc(koft(cur->_data)) % newtables.size();
cur->_next = newtables[index];
newtables[index] = cur;
cur = next;
}
_tables[i] == nullptr;
}
_tables.swap(newtables);
}
size_t index = HashFunc(koft(d)) % _tables.size();
Node* cur = _tables[index];
while (cur)
{
if (koft(cur->_data) == koft(d))
{
return make_pair(iterator(cur, this), false);
}
else
{
cur = cur->_next;
}
}
Node* newnode = new Node(d);
newnode->_next = _tables[index];
_tables[index] = newnode;
_num++;
return make_pair(iterator(newnode,this),true);
}
Node* find(const K& key)
{
KOfT koft;
size_t index = key % _tables.size();
Node* cur = _tables[index];
while (cur)
{
if (koft(cur->_data) == key)
{
return cur;
}
else
{
cur = cur->_next;
}
}
return nullptr;
}
bool erase(const K& key)
{
KOfT koft;
size_t index = key % _tables.size();
Node* cur = _tables[index];
Node* prev = nullptr;
while (cur)
{
if (koft(cur->_data) == key)
{
if (prev == nullptr)
{
_tables[index] = cur->_next;
}
else
{
prev->_next = cur->_next;
}
delete cur;
_num--;
return true;
}
else
{
prev = cur;
cur = cur->_next;
}
}
return false;
}
private:
vector<Node*> _tables;
size_t _num = 0;
};
set
#pragma once
#include "hash.h"
namespace whc
{
template <class K,class Hash = _Hash<K>>
class unordered_set
{
struct SetKOfT
{
const K& operator() (const K& key)
{
return key;
}
};
public:
typedef typename HashTable<K, K, SetKOfT, Hash>::iterator iterator;
iterator begin()
{
return _ht.begin();
}
iterator end()
{
return _ht.end();
}
pair<iterator,bool> insert(const K& key)
{
return _ht.insert(key);
}
private:
HashTable<K, K, SetKOfT,Hash> _ht;
};
}
map
#pragma once
#include <iostream>
using namespace std;
#include "hash.h"
namespace whc
{
template <class K,class V,class Hash = _Hash<K>>
class unordered_map
{
struct MapKOfT
{
const K& key operator() (const pair<K, V>& kv)
{
return kv.first;
}
};
public:
typedef typename HashTable<K, K, SetKOfT, Hash>::iterator iterator;
iterator begin()
{
return _ht.begin();
}
iterator end()
{
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;
}
private:
HashTable<K, pair<K, V>, MapKOfT,Hash> _ht;
};
}
以上还有两点细节需要说明:
第一是对于扩容的方式,因为研究表明素数的哈希冲突较为小,所以使用一个素数表来进行扩容。
第二是在map和set中还未进行模板的实例化,因此使用typedef的时候,要使用后面实例化的模板,就得在前面用typename进行声明,来告诉编译器,后面会实例化的。