1. hash表迭代器的实现
1.1 普通迭代器
// 由于迭代器的实现,需要使用哈希桶,但是哈希桶的实现,又在迭代器之后
// 因此,我们给出一个前置声明,旨在告诉系统,哈希桶已经被实现了
// 前置声明
template<class K, class T, class Hash, class KeyOfT>
class HashTable;
// 迭代器的类模板
template<class K, class T, class Hash, class KeyOfT>
struct __HTIterator
{
// 将节点的类模板类型重新定义为 Node
typedef HashNode<T> Node;
// 将迭代器的类模板类型重新定义为Self
typedef __HTIterator<K, T, Hash, KeyOfT> Self;
// 将哈希桶的类模板类型重新定义为HT
typedef HashTable<K, T, Hash, KeyOfT> HT;
// 创建一个节点
// 创建一个哈希桶
Node* _node;
HT* _ht;
// 使用构造函数来构造迭代器,初始化节点和哈希桶(必须传参的构造函数)
__HTIterator(Node* node, HT* ht)
:_node(node)
, _ht(ht)
{}
// 解引用
T& operator*()
{
// 解引用拿到data,并引用返回
return _node->_data;
}
// -> 重载
T* operator->()
{
// ->重载也就是返回data的地址
return &_node->_data;
}
bool operator != (const Self& s) const
{
// _node是节点的地址
// 此处,判断不等,直接判断两个迭代器节点的地址是否不等就可以了
return _node != s._node;
}
// ++的大逻辑就是,通过vector找到每一个桶的头节点,再通过桶的头节点来遍历每一个桶的所有节点
Self& operator++()
{
if (_node->_next)
{
// 如果说_node->_next不为空,说明当前这个桶的节点还没有全部遍历完,
// 因此我们接着向后遍历就可以了
_node = _node->_next;
}
else
{
// 代码运行到这里,说明当前桶走完了,要找下一个桶的第一个节点
// 找下一个桶的头节点的位置,就需要知道hashi,想要直到hashi
// 就需要使用仿函数KeyofT来拿到键值对中的key值
KeyOfT kot;
// hash函数对象,可以将key转化为整型
Hash hash;
// hashi = key % 哈希桶的个数 (必须先用hash函数将key转化为整型)
// 这里的_ht->_tables.size(),使用了哈希桶的私有成员函数,因此在哈希桶的类模板中,迭代器的类模板是其友元类
size_t hashi = hash(kot(_node->_data)) % _ht->_tables.size();
// 拿到当前桶的头节点对应的hashi,++之后就是下一个桶头节点存储的下标(hashi)
++hashi;
// ++之后的hashi,我们要确保其没有越界
// 也就是保证hashi < _ht->_tables.size()
while (hashi < _ht->_tables.size())
{
// 如果hashi没有越界
// 那么我们要判断,当前hashi所在的下标位置是否为空
// 如果不为空,_ht->_tables[hashi]存放的就是对应桶的头节点,这就是++后指向的节点,我们将其给到_node
// 如果_ht->_tables[hashi]为空,说明vector当前位置不存在桶,那么我们接着++hashi
// 直到这个下标位置有桶,也就是_ht->_tables[hashi]不为空,则停止++hashi
if (_ht->_tables[hashi])
{
_node = _ht->_tables[hashi];
break;
}
else
{
++hashi;
}
}
// 说明后面没有桶了,那么++后指向的就是空节点
if (hashi == _ht->_tables.size())
_node = nullptr;
}
return *this;
}
};
1.2 const_iterator
// 对于哈希桶,const迭代器不可以和普通迭代器共用一个类来实现
// 迭代器
// 前置声明
template<class K, class T, class Hash, class KeyOfT>
class HashTable;
// 为什么const迭代器没有复用?
template<class K, class T, class Ref, class Ptr, class Hash, class KeyOfT>
struct __HTIterator
{
typedef HashNode<T> Node;
// 将
typedef __HTIterator<K, T, Ref, Ptr, Hash, KeyOfT> Self;
typedef HashTable<K, T, Hash, KeyOfT> HT;
const Node* _node;
const HT* _ht;
// 此时迭代器的构造函数必须被const修饰
// 才可以构造const迭代器的begin()
// 但是初始化_node时,_node也必须被const修饰,否则node赋值给_node之后,权限会被放大
// 因此_node也必须被const修饰
__HTIterator(const Node* node, const HT* ht)
:_node(node)
, _ht(ht)
{}
// 但是如果_node和_ht都被const修饰
// 且构造函数的参数都被const修饰,
// 那么普通迭代器调用时最终返回的 Ref是T&,Ptr是T*
// 但是我们返回的_node->_data和&_node->_data却都被const修饰
// 因此,我们必须把普通迭代器和const迭代器分开写,不可以进行复用
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
bool operator != (const Self& s) const
{
return _node != s._node;
}
Self& operator++()
{
if (_node->_next)
{
_node = _node->_next;
}
else
{
// 当前桶走完了,要找下一个桶的第一个
KeyOfT kot;
Hash hash;
size_t hashi = hash(kot(_node->_data)) % _ht->_tables.size();
++hashi;
while (hashi < _ht->_tables.size())
{
if (_ht->_tables[hashi])
{
_node = _ht->_tables[hashi];
break;
}
else
{
++hashi;
}
}
// 后面没有桶了
if (hashi == _ht->_tables.size())
_node = nullptr;
}
return *this;
}
};
2.哈希桶的改造
因为我们要实现的unordered_set和unordered_map的底层都是同一个hash桶的头文件,因此我们需要对hash桶做出一些改造。
哈希桶的类模板的改造
namespace buckethash
{
/*
因为哈希桶是作为unordered_set和unordered_map的底层,因此数据类型,我们设置为模板T,这样就可以适用于unordered_set和unordered_map传递的不同的参数
*/
// 哈希桶存放的节点的类模板
template<class T>
struct HashNode
{
// pair<K, V> _kv;
// T可以实例化为键值对,也可以是其他类型的数据
T _data;
HashNode<T>* _next;
HashNode(const T& data)
:_data(data)
, _next(nullptr)
{}
};
// 哈希桶的类模板
template<class K, class T, class Hash, class KeyOfT>
class HashTable
{
// 将存储的节点的类模板类型重新定义为Node
typedef HashNode<T> Node;
// 在迭代器中 _ht->_tables.size() 这个调用了哈希桶的私有成员_tables
// 由于迭代器使用了哈希桶的私有成员
// 为了使迭代器拥有使用私有成员的权限
// 我们将迭代器的类模板设置为哈希桶的友元
// 这样迭代器就可以调用哈希桶的私有成员
template<class K, class T, class Hash, class KeyOfT>
friend struct __HTIterator;
public:
// 将迭代器的类模板类型重新定义为iterator
typedef __HTIterator<K, T, Hash, KeyOfT> iterator;
// 起始位置的迭代器
iterator begin()
{
// 找到第一个节点,并将其构造为迭代器
for (size_t i = 0; i < _tables.size(); ++i)
{
// 当_tables[i]不为空时,说明我们找到了第一个桶的头节点,
// 而_tables[i]就是第一个桶的头节点的地址
if (_tables[i])
{
// 迭代器的构造,需要传递两个参数
// 第一个参数是节点的地址,第二个参数是哈希桶的地址(指针)
// 我们创建一个迭代器的匿名对象,这个传递的效率更高
return iterator(_tables[i], this);
}
}
// 如果说这个哈希表是没有数据的,那么_tables[i]始终为空
// 则我们直接返回使用空指针和哈希桶指针构造的迭代器
return iterator(nullptr, this);
}
// 结束位置的迭代器
iterator end()
{
// 结束的迭代器,是使用空指针和哈希桶的指针来构造的
// 为什么是空指针呢,因为结束的迭代器指向的不是节点,而是空指针
return iterator(nullptr, this);
}
// 哈希桶的无参构造函数
HashTable()
:_n(0)
{
//_tables.resize(10);
_tables.resize(__stl_next_prime(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;
}
}
// 插入数据data,并返回一个键值对函数对象,first存放的是指向data的迭代器,second代表插入成功
pair<iterator, bool> Insert(const T& data)
{
// 用KeyofT来获取key元素
KeyOfT kot;
// 查到key元素,如果找到则返回key对应节点的对应的迭代器
// 当查到到,那么我们就不需要插入key
// 如果it != end()为真,说明key已经存在,我们直接返回make_pair(it, false)
// pair由迭代器和bool构造,it就是指向key的迭代器
// false说明key已经存在,我们不可以再进行插入了,代表了插入失败
iterator it = Find(kot(data));
if (it != end())
return make_pair(it, false);
// 此时说明key不存在,那么就将key进行插入
// 负载因子控制在1,超过就扩容
if (_tables.size() == _n)
{
vector<Node*> newTables;
newTables.resize(__stl_next_prime(_tables.size()), nullptr);
for (size_t 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);
newnode->_next = _tables[hashi];
_tables[hashi] = newnode;
++_n;
return make_pair(iterator(newnode, this), true);
}
// 查找到key对应的节点,返回这个节点的迭代器
iterator Find(const K& key)
{
KeyOfT kot;
// 找到key对应的hashi
size_t hashi = Hash()(key) % _tables.size();
// 拿到hashi这个下标对应的桶的头节点
Node* cur = _tables[hashi];
// 依次遍历整个桶的节点,直到cur为空
while (cur)
{
// 用KeyofT来获取T中的key
if (kot(cur->_data) == key)
{
// 如果两个key相等,则返回对应节点的迭代器
return iterator(cur, this);
}
else
{
// 当两个节点的key元素不相等,则我们接着遍历cur节点的下一个节点
cur = cur->_next;
}
}
// 如果代码运行到这里,说明没有对应的节点,则我们直接返回尾迭代器
// 尾迭代器由空指针(也就是没有对应的节点,我们已经遍历到了空指针了),和迭代器的指针构造
return end();
}
// 删除key对应的节点
bool Erase(const K& key)
{
size_t hashi = Hash()(key) % _tables.size();
Node* prev = nullptr;
Node* cur = _tables[hashi];
while (cur)
{
if (cur->_kv.first == key)
{
// 准备删除
if (cur == _tables[hashi])
{
_tables[hashi] = cur->_next;
}
else
{
prev->_next = cur->_next;
}
delete cur;
--_n;
return true;
}
else
{
prev = cur;
cur = cur->_next;
}
}
return false;
}
// 素数表
inline unsigned long __stl_next_prime(unsigned long n)
{
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
};
for (int i = 0; i < __stl_num_primes; ++i)
{
if (__stl_prime_list[i] > n)
{
return __stl_prime_list[i];
}
}
return __stl_prime_list[__stl_num_primes - 1];
}
private:
vector<Node*> _tables; // 指针数组(存放哈希桶的头节点)
size_t _n = 0;
};
}
哈希桶的完整实现
namespace buckethash
{
// 哈希桶节点的类模板
template<class T>
struct HashNode
{
T _data;
HashNode<T>* _next;
HashNode(const T& data)
:_data(data)
, _next(nullptr)
{}
};
// 前置声明(说明哈希桶已经实现了)
template<class K, class T, class Hash, class KeyOfT>
class HashTable;
// 哈希桶迭代器的类模板
template<class K, class T, class Hash, class KeyOfT>
struct __HTIterator
{
typedef HashNode<T> Node;
typedef __HTIterator<K, T, Hash, KeyOfT> Self;
typedef HashTable<K, T, Hash, KeyOfT> HT;
Node* _node;
HT* _ht;
__HTIterator(Node* node, HT* ht)
:_node(node)
, _ht(ht)
{}
T& operator*()
{
return _node->_data;
}
T* operator->()
{
return &_node->_data;
}
bool operator != (const Self& s) const
{
return _node != s._node;
}
Self& operator++()
{
if (_node->_next)
{
_node = _node->_next;
}
else
{
// 当前桶走完了,要找下一个桶的第一个
KeyOfT kot;
Hash hash;
size_t hashi = hash(kot(_node->_data)) % _ht->_tables.size();
++hashi;
while (hashi < _ht->_tables.size())
{
if (_ht->_tables[hashi])
{
_node = _ht->_tables[hashi];
break;
}
else
{
++hashi;
}
}
// 后面没有桶了
if (hashi == _ht->_tables.size())
_node = nullptr;
}
return *this;
}
};
// 哈希桶的类模板
template<class K, class T, class Hash, class KeyOfT>
class HashTable
{
// 将哈希桶的节点的类模板类型定义为Node
typedef HashNode<T> Node;
template<class K, class T, class Hash, class KeyOfT>
friend struct __HTIterator;
public:
typedef __HTIterator<K, T, Hash, KeyOfT> iterator;
iterator begin()
{
for (size_t i = 0; i < _tables.size(); ++i)
{
if (_tables[i])
{
return iterator(_tables[i], this);
}
}
return iterator(nullptr, this);
}
iterator end()
{
return iterator(nullptr, this);
}
HashTable()
:_n(0)
{
_tables.resize(__stl_next_prime(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;
}
}
pair<iterator, bool> Insert(const T& data)
{
KeyOfT kot;
iterator it = Find(kot(data));
if (it != end())
return make_pair(it, false);
// 负载因子控制在1,超过就扩容
if (_tables.size() == _n)
{
vector<Node*> newTables;
newTables.resize(__stl_next_prime(_tables.size()), nullptr);
for (size_t 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);
newnode->_next = _tables[hashi];
_tables[hashi] = newnode;
++_n;
return make_pair(iterator(newnode, this), true);
}
iterator Find(const K& key)
{
KeyOfT kot;
size_t hashi = Hash()(key) % _tables.size();
Node* cur = _tables[hashi];
while (cur)
{
if (kot(cur->_data) == key)
{
return iterator(cur, this);
}
else
{
cur = cur->_next;
}
}
return end();
}
bool Erase(const K& key)
{
size_t hashi = Hash()(key) % _tables.size();
Node* prev = nullptr;
Node* cur = _tables[hashi];
while (cur)
{
if (cur->_kv.first == key)
{
// 准备删除
if (cur == _tables[hashi])
{
_tables[hashi] = cur->_next;
}
else
{
prev->_next = cur->_next;
}
delete cur;
--_n;
return true;
}
else
{
prev = cur;
cur = cur->_next;
}
}
return false;
}
inline unsigned long __stl_next_prime(unsigned long n)
{
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
};
for (int i = 0; i < __stl_num_primes; ++i)
{
if (__stl_prime_list[i] > n)
{
return __stl_prime_list[i];
}
}
return __stl_prime_list[__stl_num_primes - 1];
}
private:
vector<Node*> _tables; // 指针数组
size_t _n = 0;
};
}
3.unordered_set的模拟实现
#include "HashTable.h"
namespace qwy
{
template<class K, class Hash = HashFunc<K>>
class unordered_set
{
// 传递给unordered_set的第一个参数类型就为K,因此仿函数直接返回K类型的值就可以
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
public:
// typename的作用
// 告诉编译器buckethash::HashTable<K, K, Hash, SetKeyOfT>::iterator是一个类型名
// 而不是一个变量名
typedef typename buckethash::HashTable<K, K, Hash, SetKeyOfT>::iterator iterator;
iterator begin()
{
// 底层调用哈希桶的begin()
return _ht.begin();
}
iterator end()
{
// 底层调用哈希桶的end()
return _ht.end();
}
pair<iterator, bool> insert(const K& key)
{
// 底层调用哈希桶的insert()
return _ht.Insert(key);
}
private:
// hash桶对象
// template<class K, class T, class Hash, class KeyOfT>
// template<class K, class Hash = HashFunc<K>>
// 第一个参数和第二个参数,都是用K进行实例化,传入缺省的HashFunc<K>函数,传入仿函数SetKeyOfT,取key值
buckethash::HashTable<K, K, Hash, SetKeyOfT> _ht;
};
void test_unordered_set()
{
unordered_set<int> us;
us.insert(13);
us.insert(3);
us.insert(23);
us.insert(5);
us.insert(5);
us.insert(6);
us.insert(15);
us.insert(223342);
us.insert(22);
unordered_set<int>::iterator it = us.begin();
while (it != us.end())
{
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : us)
{
cout << e << " ";
}
cout << endl;
}
}
4.unordered_map的模拟实现
#include "HashTable.h"
namespace qwy
{
template<class K, class V, class Hash = HashFunc<K>>
class unordered_map
{
// 传递给unordered_map的第二个参数的类型为V,也就是作为pair<const K, V>,的第二个参数
// 因此我们取pair::first也就是取pair<const K, V>中的K类型的数据
struct MapKeyOfT
{
const K& operator()(const pair<const K, V>& kv)
{
return kv.first;
}
};
public:
// typename的作用
// 告诉编译器buckethash::HashTable<K, K, Hash, SetKeyOfT>::iterator是一个类型名
// 而不是一个变量名
typedef typename buckethash::HashTable<K, pair<const K, V>, Hash, MapKeyOfT>::iterator iterator;
iterator begin()
{
return _ht.begin();
}
iterator end()
{
return _ht.end();
}
pair<iterator, bool> insert(const pair<K, V>& data)
{
return _ht.Insert(data);
}
V& operator[](const K& key)
{
// insert的返回类型为pair<iterator, bool>
// make_pair(key, V()) 其中V是根据传递的参数类型进行默认构造的
// 使用insert(),如果key已经存在,则不会进行插入
pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));
// ret.first就是迭代器
// 迭代器重载->,拿到的是_node->data的地址,也就是键值对的对象的地址
// 所以 ret.first->second 的类型就是V,或者称为value
// 所以 operator[]的返回值其实就是value的引用
return ret.first->second;
}
private:
// 私有成员为哈希桶
// template<class K, class V, class Hash = HashFunc<K>>
// template<class K, class T, class Hash, class KeyOfT>
buckethash::HashTable<K, pair<const K, V>, Hash, MapKeyOfT> _ht;
};
void test_unordered_map()
{
string arr[] = { "苹果", "西瓜", "香蕉", "草莓", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
unordered_map<string, int> countMap;
for (auto& e : arr)
{
countMap[e]++;
}
for (const auto& kv : countMap)
{
cout << kv.first << ":" << kv.second << endl;
}
}
}