之前介绍了哈希表和解决哈希冲突的两种方式
今天介绍如何封装底层为哈希表的unordered_map/set,这里是以拉链法实现的哈希表作为底层结构构(哈希桶)
哈希表的改造
一、改造HashNode
- 因为哈希表不仅要给unordered_map 使用还要给unordered_set使用,因此节点中存储的元素可能为键值对pair<K,V>,也可能只是一个值value,因此我们这里存储的值给了一个泛型,若是unordered_map调用就存储键值对pair<K,V>,若是unordered_set就存储值value。
template<class V>
struct HashNode
{
//由于之后要用哈希桶封装unordered_map/set,因此这里可能存pair<K,V>也可能存value
V _valuefiled;
HashNode<V>* _next;//哈希桶结构是一个vector数组下挂着链表,定义一个指针指向下一个节点的位置
HashNode(const V& v)
:_valuefiled(v)
, _next(nullptr)
{}
};
二、改造哈希表的Insert
- 第一个就是插入的值不再仅仅只是键值对,而是看是UnorderedMap使用红黑树时,就传pair<K,V>,UnorderedSet使用时就传value
- 第二个比较关键的改造是在于,比较所存储值的大小,我们知道map中存储的是键值对,比较大小使用键值对中的第一个值key进行比较,而set比较直接就是用该值进行的,因此这里我们就借助了一个仿函数来实现,创建一个仿函数对象,通过该对象的返回值来进行大小的比较
bool Insert(const V& v)
{
//考虑增容
CheckCapacity();
KeyOfValue kov;
const K& key = kov(v);
//size_t index = key % _table.size();
size_t index = HashIndex(key, _table.size());
Node* cur = _table[index];
while (cur)
{
if (kov(cur->_valuefiled) == key)
return false;
cur = cur->_next;
}
//走到这说明没有相同的元素,可以进行插入,
//由于哈希桶不规定产生冲突的序列有序,可以进行头插比较简单
Node* newnode = new Node(v);
newnode->_next = _table[index];
_table[index] = newnode;
++_size;
return true;
}
三、构建哈希表的迭代器
- 在实现迭代器之前,我们先得了解一个哈希表,之前也介绍了拉链法实现的哈希表其实是一个数组存储了一系列的指针,指针又链接了每个桶里产生哈希冲突的节点。
- 迭代器主要是为了遍历哈希表的元素,因此最主要是要实现迭代器的operator++,主要分为两种情况,第一种情况:要是在一个桶里面,就是产生哈希冲突的节点之间,operator++只需要返回节点的_next即可, 第二种情况:需要处理的是当已经走到一个桶的最后要寻找下一个桶的位置的情况
- 由于我们需要处理第二种情况,所有我们需要有一个指向哈希表的指针,所以迭代器中有两个成员一个是指向哈希节点的指针,一个是指向哈希表的指针。
//前置声明,由于哈希表需要迭代器,迭代器又需要哈希表
template<class K, class V, class KeyOfValue,class HashFunc>
class HashTable;
//实现哈希表的迭代器
template<class K, class V, class KeyOfValue, class HashFunc>
struct HTIterator
{
typedef HashNode<V> Node;
typedef HTIterator<K, V, KeyOfValue, HashFunc> Self;
Node* _node;//节点指针
HashTable<K, V, KeyOfValue,HashFunc>* _ht;//哈希表指针
HTIterator(Node* node, HashTable<K, V, KeyOfValue,HashFunc>* ht)
:_node(node)
, _ht(ht)
{}
V& operator*()//返回节点的值
{
return _node->_valuefil