这里的思路与前面讲解map/set的封装思路一致,STL不喜欢直接实例化出两份几乎相同的代码,所以用了模板参数来处理,还是老规矩:set中传入的是
,map中传入的是 .这样我们在哈希桶的结构中只需要用一个T类型的模板参数接受上层传入的参数即可。>
基本框架的改造:
namespace BucketHash
{
template<class T>
struct BucketHashNode
{
BucketHashNode* _next;
T _data;
BucketHashNode(const T& data)
:_next(nullptr)
,_data(data)
{}
};
template<class K>
struct HashTranfor
{
size_t operator()(const K& k)
{
return (size_t)k;
}
};
//特化一个版本出来
template<>
struct HashTranfor<string>
{
// BKDR
size_t operator()(const string& key)
{
size_t val = 0;
for (auto ch : key)
{
val *= 131;
val += ch;
}
return val;
}
};
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
};
template<class K, class T,class Hash,class KeyOfT>//给出第一个模板参数的目的是为了Insert
//时能够正确将K插入
class HashTable
{
private:
typedef BucketHashNode<T> Node;
vector<Node*> _tables;
size_t _size = 0;
public:
size_t GetCapacity(size_t sz)
{
int i = 0;
for (; i < __stl_num_primes; ++i)
{
if (sz < __stl_prime_list[i]) break;
}
return __stl_prime_list[i];
}
~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;
}
}
size_t Size()const
{
return _tables.size();
}
size_t BucketSize()const
{
int cnt = 0;
for (int i = 0; i < Size(); ++i)
{
if (_tables[i])
++cnt;
}
return cnt;
}
size_t MaxBucketSize()const
{
int maxLen = 0;
for (int i = 0; i < Size(); ++i)
{
Node* cur = _tables[i];
int len = 0;
while (cur)
{
++len;
cur = cur->_next;
}
maxLen = max(maxLen, len);
}
return maxLen;
}
};
注意:
- 为了得到set和map比较的参数所以我们设置一个模板参数KeyOfT来获取set/map比较大小的数据。
封装中最重要的一环就是迭代器了,那么究竟该如何设计迭代器呢?
首先我们来想想:迭代器究竟该有哪些成员?
由于迭代器要支持++操作(这里的迭代器不支持- -操作,因为是单向迭代器(底层用的是单链表)),所以我们得拿到整张表来方便我们遍历,我们还需要结点指针来帮助我们取数据。
所以我们就能够搭建出迭代器的大致框架了:
//前置声明
template<class K, class T, class Hash, class KeyOfT>
class HashTable;
//迭代器
template<class K, class T, class Ref, class Ptr, class Hash, class KeyOfT>
struct __TableIterator
{
typedef BucketHashNode<T> Node;
typedef HashTable<K, T, Hash, KeyOfT> HTable;
typedef __TableIterator<K, T, Ref, Ptr, Hash, KeyOfT> Self;
typedef __TableIterator<K, T, T&, T*, Hash, KeyOfT> Iterator;
Node* _node;//结点指针,方便取数据
HTable* _ht;//找到哈希表
__TableIterator(Node* node,HTable* ht)
:_node(node)
,_ht(ht)
{}
__TableIterator(const Iterator& it)
:_node(it._node)
,_ht(it._ht)
{}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
bool operator==(const Self& self)const
{
return self._node == _node;
}
bool operator!=(const Self& self)const
{
return self._node != _node;
}
};
注意:迭代器的成员变量由于用到了哈希桶结构,而哈希桶的实现在迭代器的下面,所以我们要在迭代器前面加上一个前置声明。
现在的关键是如何实现出++运算符的重载。
其实这个的实现也不难,我们已经拿到了哈希表,所以只需要从当前位置找到表中下一个位置即可:
Self& operator++()
{
if (_node->_next)
_node = _node->_next;
else
{
Hash hash;
KeyOfT kot;
size_t hashi = hash(kot(_node->_data)) % _ht->Size();
++hashi;
for (; hashi < _ht->Size(); ++hashi)
{
if (_ht->_tables[hashi])
{
_node = _ht->_tables[hashi];
break;
}
}
if (hashi == _ht->Size())
_node = nullptr;
}
return *this;
}
但是还有一个问题:我们哈希表的实现中成员变量是私有的,所以迭代器是不能够访问的,为了方便我们可以在哈希表结构中加上友元声明,当然大家自己也可以实现get接口取数据,方法有很多,大家可以自行选择。
class HashTable
{
private:
template<class K, class T, class Ref, class Ptr, class Hash, class KeyOfT>
friend struct __TableIterator;
typedef BucketHashNode<T> Node
};
template<class K, class T,class Hash,class KeyOfT>//给出第一个模板参数的目的是为了Insert
//时能够正确将K插入
class HashTable
{
private:
template<class K, class T, class Ref, class Ptr, class Hash, class KeyOfT>
friend struct __TableIterator;
typedef BucketHashNode<T> Node;
vector<Node*> _tables;
size_t _size = 0;
public:
typedef __TableIterator<K, T, T&, T*, Hash, KeyOfT> iterator;
typedef __TableIterator<K, T, const T&, const T*, Hash, KeyOfT> const_iterator;
iterator begin()
{
for (size_t i = 0; i < _tables.size(); ++i)
{
if (_tables[i])
return iterator(_tables[i], this);
}
return end();
}
iterator end()
{
return iterator(nullptr, this);
}
const_iterator begin()const
{
for (size_t i = 0; i < _tables.size(); ++i)
{
if (_tables[i])
return const_iterator(_tables[i], this);
}
return end();
}
const_iterator end()const
{
return const_iterator(nullptr, this);
}
size_t GetCapacity(size_t sz)
{
int i = 0;
for (; i < __stl_num_primes; ++i)
{
if (sz < __stl_prime_list[i]) break;
}
return __stl_prime_list[i];
}
~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;
Hash hash;
auto ret = Find(kot(data));
if (ret != end())
return make_pair(ret, false);
//扩容,负载因子为1
if (_size == _tables.size())
{
//int newcapacity = _tables.size() == 0 ? 10 : _tables.size() * 2;
int newcapacity =GetCapacity(_tables.size());
vector<Node*> newTables;//这里直接用vector的目的是可以直接用原表的结点直接链接即可
//不必多拷贝结点
newTables.resize(newcapacity);
for (int i = 0; i < _tables.size(); ++i)
{
Node* cur = _tables[i];
//头插
while (cur)
{
Node* next = cur->_next;
size_t hashi = hash(kot(cur->_data)) % newTables.size();
cur->_next = newTables[hashi];
newTables[hashi] = cur;
cur=next;
}
_tables[i] = nullptr;
}
_tables.swap(newTables);
}
size_t hashi = hash(kot(data)) % _tables.size();
Node* newNode = new Node(data);//这里不要加kot
newNode->_next = _tables[hashi];
_tables[hashi] = newNode;
++_size;
return make_pair(iterator(newNode,this), true);
}
iterator Find(const K& k)
{
if (_tables.size()==0)
return end();
KeyOfT kot;
Hash hash;
size_t hashi = hash(k) % _tables.size();
Node* cur = _tables[hashi];
while (cur)
{
if (kot(cur->_data) == k)
return iterator(cur,this);
cur = cur->_next;
}
return end();
}
bool Erase(const K& k)
{
if (Find(k) == nullptr)
return false;
KeyOfT kot;
Hash hash;
size_t hashi = hash(k) % _tables.size();
if (kot(_tables[hashi]->_data) == k)
{
Node* del = _tables[hashi];
_tables[hashi] = del->_next;
delete del;
--_size;
return true;
}
Node* cur = _tables[hashi];
while (cur->_next && kot(cur->_next->_data) != k)
{
cur = cur->_next;
}
Node* del = cur->_next;
cur->_next = del->_next;
delete del;
--_size;
return true;
}
size_t Size()const
{
return _tables.size();
}
size_t BucketSize()const
{
int cnt = 0;
for (int i = 0; i < Size(); ++i)
{
if (_tables[i])
++cnt;
}
return cnt;
}
size_t MaxBucketSize()const
{
int maxLen = 0;
for (int i = 0; i < Size(); ++i)
{
Node* cur = _tables[i];
int len = 0;
while (cur)
{
++len;
cur = cur->_next;
}
maxLen = max(maxLen, len);
}
return maxLen;
}
};
注意:由于迭代器的构造中我们需要哈希表的指针,而恰好this就是哈希表指针,所以我们传入this即可。另外find和inset时为了与STL保持一致,我们将返回值分别修改为iterator和pair
这个思路与红黑树种map/set的封装思路一致,如何实现unordered_map支持修改val,不支持修改key;而unordered_set不支持修改key也是一致的:unordered_map的pair中的参数给
,而unordered_set的普通迭代器是用const迭代器实现的,const迭代器也是用const迭代器实现的。
但是unordered_set中这样实现就会有一些问题:我们用了普通迭代器构造了const迭代器,这样肯定是会编译报错的,所以在迭代器的中我们还得增加一个构造:
typedef __TableIterator<K, T, Ref, Ptr, Hash, KeyOfT> Self;
typedef __TableIterator<K, T, T&, T*, Hash, KeyOfT> Iterator;
Node* _node;//结点指针
HTable* _ht;//找到哈希表
__TableIterator(const Iterator& it)
:_node(it._node)
,_ht(it._ht)
{}
这样如果上层传入的是普通迭代器那么就是一个拷贝构造,传入的是const迭代器的话就是用了普通迭代器构造const迭代器。这点大家一定要注意。
template<class K, class V, class Hash = BucketHash::HashTranfor<K>>
class unordered_map
{
struct MapKeyOfT
{
const K& operator()(const pair<K,V>& kv)
{
return kv.first;
}
};
private:
BucketHash::HashTable<K, pair<const K,V>, Hash, MapKeyOfT> _tab;
public:
typedef typename BucketHash::HashTable<K, pair<const K,V>, Hash, MapKeyOfT>::iterator iterator;
//这里要加上typename 的原因是告诉编译器这里是模板的声明而不是静态成员的声明
iterator begin()
{
return _tab.begin();
}
iterator end()
{
return _tab.end();
}
pair<iterator, bool> insert(const pair<K,V>& kv)
{
return _tab.Insert(kv);
}
V& operator[](const K& k)
{
auto ret = _tab.Insert(make_pair(k, V()));
return ret.first->second;
}
};
template<class K, class Hash = BucketHash::HashTranfor<K>>
class unordered_set
{
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
private:
BucketHash::HashTable<K, K, Hash, SetKeyOfT> _tab;
public:
typedef typename BucketHash::HashTable<K, K, Hash, SetKeyOfT>::const_iterator iterator;
//这里要加上typename 的原因是告诉编译器这里是模板的声明而不是静态成员的声明
typedef typename BucketHash::HashTable<K, K, Hash, SetKeyOfT>::const_iterator const_iterator;
iterator begin()
{
return _tab.begin();
}
iterator end()
{
return _tab.end();
}
const_iterator begin()const
{
return _tab.begin();
}
const_iterator end()const
{
return _tab.end();
}
pair<iterator, bool> insert(const K& k)
{
return _tab.Insert(k);
}
};
好了,这样封装基本上大致框架也就完成了。
如果该文对你有帮助的话能不能一键三连支持博主呢?