目录
3.4 实现迭代器Iterator/const_Iterator
1. 整体学习的思维导图
2. 链地址法哈希表
本次的封装实现基于上次实现完成的链地址法的哈希表,以下是生成实现的整体代码,具体分析参考哈希表那章。
namespace Hashbucket
{
//
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 HashTable
{
typedef HashNode<K, V> Node;
public:
HashTable()
:_tables(11)
,_n(0)
{}
~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;
}
}
HashTable(const HashTable& HT)
:_tables(HT._tables.size())
{
for (size_t i = 0; i < HT._tables.size(); i++)
{
Node* cur = HT._tables[i];
while (cur)
{
Insert(cur->_kv);
cur = cur->_next;
}
}
}
HashTable& operator=(const HashTable& HT)
{
if (this != &HT)
{
// 释放旧数据
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;
}
// 插入新数据
for (size_t i = 0; i < HT._tables.size(); i++)
{
Node* cur = HT._tables[i];
while (cur)
{
Insert(cur->_kv);
cur = cur->_next;
}
}
}
return *this;
}
bool Insert(const pair<K, V>& kv)
{
if (Find(kv.first))
return false;
// 扩容
if (_n == _tables.size())
{
// 复用法
// 会创建新的节点+使用旧的节点
/*HashTable<K, V> newHT;
newHT._tables.resize(_tables.size() * 2);
for (size_t i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
while (cur)
{
newHT.Insert(cur->_kv);
cur = cur->_next;
}
}
_tables.swap(newHT._tables);*/
// 摘取法
// 使用旧的节点
vector<Node*> newtables(_tables.size() * 2);
for (size_t i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
while (cur)
{
Node* next = cur->_next;
// 重新计算出映射位置hashi
size_t hashi = kv.first % newtables.size();
cur->_next = newtables[hashi];
newtables[hashi] = cur;
cur = next;
}
_tables[i] = nullptr;
}
_tables.swap(newtables);
}
// 计算出映射位置hashi,这个计算必须在扩容下面不然映射会出错
size_t hashi = kv.first % _tables.size();
// 创建一个新的节点
Node* newnode = new Node(kv);
// 头插法
newnode->_next = _tables[hashi]; // 新节点指向原来的节点
_tables[hashi] = newnode; // 新节点成为新的头结点
++_n; // ++节点数
return true;
}
Node* Find(const K& key)
{
// 算出hashi
size_t hashi = key % _tables.size();
Node* cur = _tables[hashi];
while (cur)
{
if (cur->_kv.first == key)
return cur;
cur = cur->_next;
}
return nullptr;
}
bool Erase(const K& key)
{
// 算出hashi
size_t hashi = key % _tables.size();
Node* prev = nullptr;
Node* cur = _tables[hashi];
while (cur)
{
if (cur->_kv.first == key)
{
if (prev == nullptr)
{
_tables[hashi] = cur->_next;
}
else
{
prev->_next = cur->_next;
}
delete cur;
--_n;
return true;
}
prev = cur;
cur = cur->_next;
}
return false;
}
private:
vector<Node*> _tables; // 指针数组
size_t _n = 0; // 记录个数
};
}
3. 封装实现
3.1 解决key/pair问题
由于unordered_set和unordered_map所需要的参数分别为key和pair,所以我们要让底层的哈希表同时支持两者,我们要将模版参数中的数据类型参数改为T。
template<class T>
struct HashNode
{
T _data;
HashNode<T>* _next;
HashNode(const T& data)
:_data(data)
, _next(nullptr)
{}
};
template<class K, class T>
class HashTable
{
// .......
typedef HashNode<T> Node;
bool Insert(const T& data);
Node* Find(const K& key);
bool Erase(const K& key);
// ......
};
3.2 解决取key/pair.first问题
由于我们需要使用key来计算哈希表的映射位置,但是unordered_set/unordered_map使用的数据类型不同,需要根据不同情况取出key,我们需要再传一个仿函数GetOfKey来解决这个问题。
template<class K, class V>
class Myunordered_map
{
public:
// 仿函数用于获取key
struct getofkey
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
bool Insert(const pair<K, V>& kv)
{
return _tb.Insert(kv);
}
private:
HashTable<K, pair<K, V>, getofkey> _tb;
};
template<class K>
class Myunordered_set
{
public:
// 仿函数用于获取key
struct getofkey
{
const K& operator()(const K& key)
{
return key;
}
};
bool Insert(const K& key)
{
return _tb.Insert(key);
}
private:
HashTable<K, K, getofkey> _tb;
};
3.3 解决类型转换的问题
对于以上的哈希表存储正整数没有问题,但是遇到负数,字符串等等需要进行特殊处理才可以映射,因此我们需要再传入一个参数用于转换类型为正整数ChangeOfType。该模版参数应用于取模计算hashi时。
vector<string> v = { "Hello", "string", "left", "right" };
ouyang::Myunordered_set<string> st;
for (const auto& e : v)
{
st.Insert(e);
}
template<class K>
struct changeoftype
{
size_t operator()(const K& key)
{
return (size_t)key;
}
};
// 模版特化string
template<>
struct changeoftype<string>
{
size_t operator()(const string& s)
{
size_t key = 0;
for (auto ch : s)
{
key += ch;
key *= 131;
}
return key;
}
};
把该模版参数传参位置设定在外壳部分->unordered_set/unordered_map,方便后续自定义类型设计仿函数进行类型转换。
template<class K, class V, class ChangeOfType = changeoftype<K>>
class Myunordered_map
{
public:
// ....
};
template<class K, class ChangeOfType = changeoftype<K>>
class Myunordered_set
{
public:
// ....
};
// 哈希表对象
template<class K, class T, class GetOfKey, class ChangeOfType>
class HashTable
{
// ....
};
3.4 实现迭代器Iterator/const_Iterator
3.4.1 分析迭代器的++
-
情况一:当前链表没有走到空,继续往下走
-
情况二:当前链表走到了空,继续找寻下一个挂着链表的哈希映射位
-
【1】找到哈希映射位继续重复情况一
-
【2】找到尾巴_tables.size()都没有挂着链表的哈希映射位结束
-
这个过程中我们需要当前的节点_node和整个哈希表的大小。
template<class K, class T, class Ref, class Ptr, class GetOfKey, class changeoftype>
struct HashIterator
{
typedef HashNode<T> Node;
typedef HashIterator<K, T, Ref, Ptr, GetOfKey, changeoftype> Self;
Node* _node;
const HashTable<K, T, GetOfKey, changeoftype>* _HT;
HashIterator(Node* node, const HashTable<K, T, GetOfKey, changeoftype>* HT)
:_node(node)
, _HT(HT)
{}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
bool operator!=(const Self& s)
{
return _node != s._node;
}
bool operator==(const Self& s)
{
return _node == s._node;
}
Self& operator++()
{
if (_node->_next)
{
// 如果当前桶链还有节点继续往下走
_node->_next;
}
else
{
// 当前桶链没有节点了,找寻下一个存在的桶链
GetOfKey gok;
changeoftype cot;
size_t hashi = cot(gok(_node->_data)) % _HT->_tables.size();
++hashi;
while (hashi < _HT->_tables.size())
{
if (_HT->_tables[hashi])
break;
++hashi;
}
// 不在有效范围内
if (hashi == _HT->_tables.size())
{
_node = nullptr; // end()
}
else { // 如果哈希映射位在有效范围内
_node = _HT->_tables[hashi];
}
}
return *this;
}
};
并且我们需要在HashTable处声明友元,HashIterator中访问到了HashTable中的私有成员_n和_tables。
// 友元->HashTable和HashIterator都互相依赖对方,HashIterator会访问私有成员_n和_tables。
template<class K, class T, class Ref, class Ptr, class GetOfKey, class changeoftype>
friend struct HashIterator;
3.5 实现map的operator[]
V& operator[](const K& key)
{
pair<Iterator, bool> ret = _tb.Insert({ key, V() });
return ret.first->second;
}
4. 整体实现
4.1 HashTable.h
#pragma once
#include <iostream>
#include <vector>
using namespace std;
// 用于获取素数
inline unsigned long __stl_next_prime(unsigned long n)
{
// Note: assumes long is at least 32 bits.
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
};
const unsigned long* first = __stl_prime_list;
const unsigned long* last = __stl_prime_list + __stl_num_primes;
const unsigned long* pos = lower_bound(first, last, n);
return pos == last ? *(last - 1) : *pos;
}
// 类型转化
template<class K>
struct changeoftype
{
size_t operator()(const K& key)
{
return (size_t)key;
}
};
// 模版特化:string
template<>
struct changeoftype<string>
{
size_t operator()(const string& s)
{
size_t key = 0;
for (auto ch : s)
{
key += ch;
key *= 131;
}
return key;
}
};
// 哈希节点
template<class T>
struct HashNode
{
T _data;
HashNode<T>* _next;
HashNode(const T& data)
:_data(data)
, _next(nullptr)
{}
};
// 哈希表对象
template<class K, class T, class GetOfKey, class ChangeOfType>
class HashTable
{
// 友元->HashTable和HashIterator都互相依赖对方
// HashIterator会通过_HT访问HashTable的私有成员_n和_tables。
template<class K, class T, class Ref, class Ptr, class GetOfKey, class changeoftype>
friend struct HashIterator;
typedef HashNode<T> Node;
public:
// Iterator和ConstIterator需要放在public,方便map和set使用
typedef HashIterator<K, T, T&, T*, GetOfKey, ChangeOfType> Iterator;
typedef HashIterator<K, T, const T&, const T*, GetOfKey, ChangeOfType> ConstIterator;
Iterator Begin()
{
// 哈希表中数据为0时
if (_n == 0)
return End();
// 找寻哈希表中第一个不为空的映射位置
for (size_t i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
if (cur)
{
return Iterator(cur, this);
}
}
// 找不到返回End()
return End();
}
Iterator End()
{
// 定义End为nullptr
return Iterator(nullptr, this);
}
ConstIterator Begin() const
{
// 哈希表中数据为0时
if (_n == 0)
return End();
// 找寻哈希表中第一个不为空的映射位置
for (size_t i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
if (cur)
{
return Iterator(cur, this);
}
}
// 找不到返回End()
return End();
}
ConstIterator End() const
{
// 定义End为nullptr
return Iterator(nullptr, this);
}
public:
// 构造
HashTable()
:_tables(__stl_next_prime(0))
, _n(0)
{}
// 析构
~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;
}
}
// 拷贝构造
HashTable(const HashTable& other)
:_tables(other._tables.size()) // 申请和other一样的空间大小
{
// 获取key
GetOfKey gok;
// 转换类型
ChangeOfType cot;
for (size_t i = 0; i < other._tables.size(); i++)
{
Node* cur = other._tables[i];
while (cur)
{
Insert(cur->_data);
cur = cur->_next;
}
}
}
// 赋值运算符重载
HashTable& operator=(const HashTable& other)
{
if (this != &other)
{
// 先清理当前哈希表的资源
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;
}
_tables = vector<Node*>(__stl_next_prime(other._tables.size()));
GetOfKey gok;
ChangeOfType cot;
// 遍历源哈希表的每个桶
for (size_t i = 0; i < other._tables.size(); ++i)
{
Node* cur = other._tables[i];
while (cur)
{
// 插入当前节点的数据到新哈希表
Insert(cur->_data);
cur = cur->_next;
}
}
}
return *this;
}
// 插入
pair<Iterator, bool> Insert(const T& data)
{
// 获取key
GetOfKey gok;
// 转换类型
ChangeOfType cot;
// 先查找是否存在,不实现mult版本
Iterator ret = Find(gok(data));
if (ret != End())
return make_pair(ret, false);
// 扩容
if (_n == _tables.size())
{
// 摘取法
// 使用旧的节点
vector<Node*> newtables(__stl_next_prime(_tables.size()) + 1);
for (size_t i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
while (cur)
{
Node* next = cur->_next;
// 重新计算出映射位置hashi
size_t hashi = cot(gok(data)) % newtables.size();
cur->_next = newtables[hashi];
newtables[hashi] = cur;
cur = next;
}
_tables[i] = nullptr;
}
_tables.swap(newtables);
}
// 计算出映射位置hashi,这个计算必须在扩容下面不然映射会出错
size_t hashi = cot(gok(data)) % _tables.size();
// 创建一个新的节点
Node* newnode = new Node(data);
// 头插法
newnode->_next = _tables[hashi]; // 新节点指向原来的节点
_tables[hashi] = newnode; // 新节点成为新的头结点
++_n; // ++节点数
return { Iterator(newnode, this), true };
}
// 查找
Iterator Find(const K& key)
{
GetOfKey gok;
ChangeOfType cot;
// 算出hashi
size_t hashi = cot(key) % _tables.size();
Node* cur = _tables[hashi];
while (cur)
{
if (gok(cur->_data) == key)
return Iterator(cur, this);
cur = cur->_next;
}
return End();
}
// 删除
bool Erase(const K& key)
{
GetOfKey gok;
ChangeOfType cot;
// 算出hashi
size_t hashi = cot(key) % _tables.size();
Node* prev = nullptr;
Node* cur = _tables[hashi];
while (cur)
{
if (gok(cur->_data) == key)
{
if (prev == nullptr)
{
_tables[hashi] = cur->_next;
}
else
{
prev->_next = cur->_next;
}
delete cur;
--_n;
return true;
}
prev = cur;
cur = cur->_next;
}
return false;
}
private:
vector<Node*> _tables; // 指针数组
size_t _n = 0; // 记录个数
};
// Hash迭代器
template<class K, class T, class Ref, class Ptr, class GetOfKey, class changeoftype>
struct HashIterator
{
typedef HashNode<T> Node;
typedef HashIterator<K, T, Ref, Ptr, GetOfKey, changeoftype> Self;
Node* _node;
const HashTable<K, T, GetOfKey, changeoftype>* _HT;
HashIterator(Node* node, const HashTable<K, T, GetOfKey, changeoftype>* HT)
:_node(node)
, _HT(HT)
{}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
bool operator!=(const Self& s)
{
return _node != s._node;
}
bool operator==(const Self& s)
{
return _node == s._node;
}
Self& operator++()
{
if (_node->_next)
{
// 如果当前桶链还有节点返回
_node = _node->_next;
}
else
{
// 当前桶链没有节点了,找寻下一个存在的桶链
GetOfKey gok;
changeoftype cot;
size_t hashi = cot(gok(_node->_data)) % _HT->_tables.size();
++hashi;
while (hashi < _HT->_tables.size())
{
if (_HT->_tables[hashi])
break;
++hashi;
}
// 不在有效范围内
if (hashi == _HT->_tables.size())
{
_node = nullptr; // end()
}
else { // 如果哈希映射位在有效范围内
_node = _HT->_tables[hashi];
}
}
return *this;
}
};
4.2 Myunordered_map.h
#pragma once
#include "HashTable.h"
namespace ouyang
{
template<class K, class V, class ChangeOfType = changeoftype<K>>
class Myunordered_map
{
// getodkey需要放在typedef前面,因为是模版参数之一
// 仿函数用于获取key
struct getofkey
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
public:
// typename 事先声明存在该对象,等模版实例化再去寻找。
typedef typename HashTable<K, pair<const K, V>, getofkey, ChangeOfType>::Iterator iterator;
typedef typename HashTable<K, pair<const K, V>, getofkey, ChangeOfType>::ConstIterator const_iterator;
pair<iterator, bool> Insert(const pair<K, V>& kv)
{
return _tb.Insert(kv);
}
iterator Find(const K& key)
{
return _tb.Find(key);
}
bool Erase(const K& key)
{
return _tb.Erase(key);
}
iterator begin()
{
return _tb.Begin();
}
iterator end()
{
return _tb.End();
}
const_iterator begin() const
{
return _tb.Begin();
}
const_iterator end() const
{
return _tb.End();
}
V& operator[](const K& key)
{
pair<iterator, bool> ret = _tb.Insert({ key, V() });
return ret.first->second;
}
private:
HashTable<K, pair<const K, V>, getofkey, ChangeOfType> _tb;
};
}
4.3 Myunordered_set.h
#pragma once
#include "HashTable.h"
namespace ouyang
{
template<class K, class ChangeOfType = changeoftype<K>>
class Myunordered_set
{
// getodkey需要放在typedef前面,因为是模版参数之一
// 仿函数用于获取key
struct getofkey
{
const K& operator()(const K& key)
{
return key;
}
};
public:
// typename 事先声明存在该对象,等模版实例化再去寻找。
typedef typename HashTable<K, const K, getofkey, ChangeOfType>::Iterator iterator;
typedef typename HashTable<K, const K, getofkey, ChangeOfType>::ConstIterator const_iterator;
pair<iterator, bool> Insert(const K& key)
{
return _tb.Insert(key);
}
iterator Find(const K& key)
{
return _tb.Find(key);
}
bool Erase(const K& key)
{
return _tb.Erase(key);
}
iterator begin()
{
return _tb.Begin();
}
iterator end()
{
return _tb.End();
}
const_iterator begin() const
{
return _tb.Begin();
}
const_iterator end() const
{
return _tb.End();
}
private:
HashTable<K, const K, getofkey, ChangeOfType> _tb;
};
}