文章目录
一、哈希表的修改
如下是我们一开始的哈希表
cpp
namespace hash_bucket
{
template<class K,class V>
struct HashNode
{
pair<K, V> _kv;
HashNode<K, V>* _next = nullptr;
HashNode(const pair<K, V>& kv)
:_kv(kv)
{}
};
template<class K>
struct DefaultHashFunc
{
size_t operator()(const K& key)
{
return (size_t)key;
}
};
template<>
struct DefaultHashFunc<string>
{
size_t operator()(const string& str)
{
size_t hashi = 0;
for (auto ch : str)
{
hashi = hashi * 131 + ch;
}
return hashi;
}
};
template<class K, class V, class HashFunc = DefaultHashFunc<K>>
class HashTable
{
typedef HashNode<K,V> Node;
public:
HashTable()
:_n(0)
{
_table.resize(10, nullptr);
}
bool Insert(const pair<K, V>& kv)
{
if (Find(kv.first))
{
return false;
}
HashFunc hf;
if (_n == _table.size())
{
size_t newSize = _table.size() * 2;
vector<Node*> newTable(newSize, nullptr);
for (int i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
while (cur)
{
Node* next = cur->_next;
size_t hashi = hf(cur->_kv.first) % newTable.size();
cur->_next = newTable[hashi];
newTable[hashi] = cur;
cur = next;
}
_table[i] = nullptr;
}
_table.swap(newTable);
}
size_t hashi = hf(kv.first) % _table.size();
Node* newnode = new Node(kv);
newnode->_next = _table[hashi];
_table[hashi] = newnode;
++_n;
return true;
}
void Print()
{
for (int i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
printf("[%d]->", i);
while (cur)
{
cout << cur->_kv.first << ":" << cur->_kv.second << "->";
cur = cur->_next;
}
cout << "NULL" << endl;
}
}
Node* Find(const K& key)
{
HashFunc hf;
size_t hashi = hf(key) % _table.size();
Node* cur = _table[hashi];
while (cur)
{
if (cur->_kv.first == key)
{
return cur;
}
cur = cur->_next;
}
return nullptr;
}
bool Erase(const K& key)
{
HashFunc hf;
size_t hashi = hf(key) % _table.size();
Node* cur = _table[hashi];
Node* prev = nullptr;
while (cur)
{
if (cur->_kv.first == key)
{
if (prev)
{
prev->_next = cur->_next;
}
else
{
_table[hashi] = cur->_next;
}
delete cur;
cur = nullptr;
_n--;
return true;
}
else
{
prev = cur;
cur = cur->_next;
}
}
return false;
}
~HashTable()
{
for (int i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
while (cur)
{
Node* next = cur->_next;
delete cur;
cur = next;
}
_table[i] = nullptr;
}
}
private:
vector<Node*> _table;
size_t _n;
};
}
现在为了将他改装位unordered系列容器,我们还需要对这个哈希表做出一些修改
对于哈希表的修改与红黑树去封装map和set是非常类似的。
首先是将结点都改为T类型的,这个T类型对于set而言是K,对于map而言是pair<K,V>
然后接下来我们继续修改其他接口,我们先来修改插入接口,首先是该类型为T类型
但是这样的话,会导致,没有kv了,而Find接口需要一个K类型的,对于这个问题,我们与红黑树的解决办法一样
然后我们去修改Insert里面的问题,最终修改为如下所示
cpp
bool Insert(const T& data)
{
KeyOfT kot;
if (Find(kot(data)))
{
return false;
}
HashFunc hf;
if (_n == _table.size())
{
size_t newSize = _table.size() * 2;
vector<Node*> newTable(newSize, nullptr);
for (int i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
while (cur)
{
Node* next = cur->_next;
size_t hashi = hf(kot(cur->_data)) % newTable.size();
cur->_next = newTable[hashi];
newTable[hashi] = cur;
cur = next;
}
_table[i] = nullptr;
}
_table.swap(newTable);
}
size_t hashi = hf(kot(data)) % _table.size();
Node* newnode = new Node(data);
newnode->_next = _table[hashi];
_table[hashi] = newnode;
++_n;
return true;
}
接下来修改Find函数,加上kot的逻辑即可
cpp
Node* Find(const K& key)
{
HashFunc hf;
KeyOfT kot;
size_t hashi = hf(key) % _table.size();
Node* cur = _table[hashi];
while (cur)
{
if (kot(cur->_data) == key)
{
return cur;
}
cur = cur->_next;
}
return nullptr;
}
然后是删除逻辑
cpp
bool Erase(const K& key)
{
HashFunc hf;
KeyOfT kot;
size_t hashi = hf(key) % _table.size();
Node* cur = _table[hashi];
Node* prev = nullptr;
while (cur)
{
if (kot(cur->_data) == key)
{
if (prev)
{
prev->_next = cur->_next;
}
else
{
_table[hashi] = cur->_next;
}
delete cur;
cur = nullptr;
_n--;
return true;
}
else
{
prev = cur;
cur = cur->_next;
}
}
return false;
}
有了上面的改造哈希表,我们就可以先暂时的使用一下unordered_map和set
cpp
void test1()
{
Sim::unordered_map<int, int> um;
um.insert(make_pair(1, 1));
um.insert(make_pair(5, 5));
um.insert(make_pair(2, 2));
um.insert(make_pair(3, 3));
Sim::unordered_set<int> us;
us.insert(1);
us.insert(5);
us.insert(3);
us.insert(2);
}
二、迭代器
1.普通迭代器
对于哈希表的迭代器,我们需要注意的有以下几点
首先是最为核心的功能++,他该如何++是个麻烦,如果是再当前桶的下一个还有结点,那是最好的情况,可以直接向后指即可。但是如果当前桶已经完了,如何去找下一个桶呢?
我们的办法是这样的,再去定义一个哈希表的指针,因为有了哈希表的指针,我们就可以去访问哈希表中的容量,根据我们当前迭代器指向的数据,我们可以计算出当前在哪一个桶里面,然后我们在从下一个桶里开始,去依次寻找桶中有数据的元素即可。
如下就是迭代器的设计
cpp
template<class K, class T, class KeyOfT, class HashFunc>
struct HTIterator
{
typedef HashNode<T> Node;
typedef HTIterator<K, T, KeyOfT, HashFunc> Self;
Node* _node;
HashTable<K, T, KeyOfT, HashFunc>* _pht;
HTIterator(Node* node, HashTable<K, T, KeyOfT, HashFunc>* pht)
:_node(node)
,_pht(pht)
{}
Self& operator++()
{
if (_node->_next)
{
_node = _node->_next;
}
else
{
KeyOfT kot;
HashFunc hf;
size_t hashi = hf(kot(_node->_data)) % _pht->_table.size();
++hashi;
while (hashi < _pht->_table.size())
{
if (_pht->_table[hashi])
{
_node = _pht->_table[hashi];
return *this;
}
++hashi;
}
_node = nullptr;
}
return *this;
}
bool operator!=(const Self& ht)
{
return _node != ht._node;
}
bool operator==(const Self& ht)
{
return _node == ht._node;
}
T& operator*()
{
return _node->_data;
}
T* operator->()
{
return &_node->_data;
}
};
不过需要特别注意的是,我们必须得在哈希表中有一个友元声明,因为迭代器中需要去通过访问哈希表的底层数组的大小。而这个数组是一个私有成员变量,所以我们需要使用友元。
还需要注意的是,在迭代器中,我们使用了哈希表指针,在哈希表中,使用了迭代器。因为迭代器中只用到了哈希表的指针,所以需要在迭代器前面有一个哈希表的前置声明,让迭代器中的类型可以通过编译。
如下就是map和set中的迭代器函数了
使用如下的测试用例
cpp
void test1()
{
Sim::unordered_map<int, int> um;
um.insert(make_pair(1, 1));
um.insert(make_pair(5, 5));
um.insert(make_pair(2, 2));
um.insert(make_pair(3, 3));
Sim::unordered_map<int, int>::iterator umit = um.begin();
while (umit != um.end())
{
cout << umit->first << ":" << umit->second << endl;
++umit;
}
cout << endl;
Sim::unordered_map<string, string> ums;
ums.insert(make_pair("sort", "排序"));
ums.insert(make_pair("insert", "插入"));
ums.insert(make_pair("delete", "删除"));
ums.insert(make_pair("begin", "开始"));
for (auto e : ums)
{
cout << e.first << ":" << e.second << endl;
}
cout << endl;
Sim::unordered_set<int> us;
us.insert(1);
us.insert(5);
us.insert(3);
us.insert(2);
Sim::unordered_set<int>::iterator usit = us.begin();
while (usit != us.end())
{
cout << *usit << " ";
++usit;
}
cout << endl;
}
运行结果如下所示
2.const迭代器
看似上面的代码已经可以跑了,但是还是存在一些问题,因为我们还没有解决const迭代器的问题,这就导致了,在set中key可以被修改,在map中,key也可以被修改,这是一个很严重的问题。所以现在我们需要来解决他
我们先来处理const迭代器的问题
const的迭代器的处理思路还是和之前一样的,多传两个模板参数来解决
这样一来,我们就成功的将普通迭代器和const迭代器都处理好了
如下所示,我们再将set中的迭代器给处理好,让无论是const对象还是普通对象,都只能去访问const迭代器。无论是const迭代器还是普通迭代器其实都是const迭代器
不过在这里我们会会遇到一个新的问题
这里的问题其实报错报的不是很明显,下面这个更详细一些
其实这里是因为,this指针出现了权限放大的问题
因为我们set要使用的时候统统都把this指针当成了const修饰以后的this指针。而我们迭代器的构造函数中这个哈希表指针的类型是一个普通的类型,所以权限放大了
那么我们该如何解决呢?
有两种思路,一种是直接重载然后强制类型转换
这样的编译器也确实可以通过
还有一种方案就是直接将原来的内置成员类型改为const指针
为什么敢将这个指针改为const呢?
因为我们这个迭代器类中,并没有通过这个指针去修改哈希表中的任何事情,我们仅仅只是利用这个哈希表指针去读取哈希表的大小以及一些结点的指针。所以我们才敢将这个指针改为const
我们在这个迭代器中一切的修改都是使用node指针去进行修改的,只有通过它的解引用才可以实现
如此一来,哈希表中的迭代器已经可以跑起来了,那么问题又来了,可不可以不要第一个构造呢?
其实是可以的,如果没有第一个构造函数,因为第二个也是可以用的,因为仅仅这里就只是权限的缩小。即便普通迭代器也是可以传过来的
前面是对于set的处理,现在来处理以下map的,对于map的就很简单了,我们直接加上一个const即可,这样的话对于普通的map对象,由于底层第一个参数是const类型的,那么即便迭代器返回了,也是无法修改的。
3.插入返回值
解决了const迭代器的问题,接下来我们会发现我们的插入的返回值还是一个bool值,事实上应该是一个pair类型的,这一点也是为了支撑map的[]运算符重载
如下所示,我们先改变哈希表的insert
要改变哈希表的insert,我们还需要去改变find的返回值,它应该返回一个迭代器
不过在这里会出现跟使用红黑树封装set时一样的问题
这里是因为unordered_set中的iterator其实是const_iterator。所以是两个完全不一样的类型。当然不可以直接进行转化了。
不过在这里我们可能会有一些疑惑,为什么下面这段代码应该也涉及到了这个问题,但是在之前的测试中没有任何问题呢?
上面的这个问题可以等效为下面的问题
明明是两个不一样的类型,为什么不会报错呢?
其实这就是拷贝构造函数的妙用了
在pair<K,V>类中,其实类里面重载了两个参数都为const,或者其中一个为const的情况,这里的其实就是一个利用普通对象去构造const对象的妙用。
上面的问题还可以进一步在list的代码中找到
在这里,我们之前模拟实现过list,我们也知道,这里的迭代器其实也是一个对象,lt是一个普通的对象,它的bengin自然就是一个普通迭代器,那么第三行代码中,普通迭代器对象可以直接赋值给const迭代器对象。我们知道里面的这两个迭代器是完全不同的两个对象,至于他们为什么会支持,就是因为重载了一个拷贝构造函数,当这个迭代器为普通迭代器的时候,它其实没有什么用,就算不写它编译器也会默认生成一个拷贝构造函数。当这个迭代器为const迭代器的时候,那么这里就不是拷贝构造函数了,而是构造函数,这个构造函数是利用一个普通迭代器去构造一个const迭代器的。
类似的场景我们在前面使用红黑树的时候我们也使用过
我们当时也是通过添加一个拷贝构造函数来完成的
所以本来模板实例化后不同的类型是不可以相互转化的,但是由于拷贝构造函数的妙用,让这个拷贝构造函数有了双重意义以后,便行得通了。使得模板参数中带有const的类型可以使用普通的进行构造。
类似与下面的代码
好了现在回过头来,虽然举了四个例子,但是目标还是不能忘了,我们引发上面思考的原因就是因为类型不匹配问题所导致的,现在我们有了上面的如何用普通迭代器构造const迭代器的思路以后。我们能否将这个思路运用到我们的代码上呢?毕竟我们现在代码遇到的问题就是哈希表返回的是一个普通迭代器,而我们set需要返回一个const迭代器。
即现在,我们需要用这个普通迭代器构造一个const迭代器,那么我们就需要写一个具有双重意义的拷贝构造函数了
有了在这个拷贝构造函数以后,我们在insert中这样写就可以了
至此,我们的迭代器就终于实现了!!!
4.unordered_map的operator[]
这个有了insert的基础以后,就非常简单了
测试也是没有任何问题的
三、完整代码
HashTable.cpp文件
cpp
#pragma once
namespace hash_bucket
{
template<class K>
struct DefaultHashFunc
{
size_t operator()(const K& key)
{
return (size_t)key;
}
};
template<>
struct DefaultHashFunc<string>
{
size_t operator()(const string& str)
{
size_t hashi = 0;
for (auto ch : str)
{
hashi = hashi * 131 + ch;
}
return hashi;
}
};
template<class T>
struct HashNode
{
T _data;
HashNode<T>* _next = nullptr;
HashNode(const T& data)
:_data(data)
{}
};
//前置声明
template<class K, class T, class KeyOfT, class HashFunc>
class HashTable;
template<class K, class T, class Ptr, class Ref, class KeyOfT, class HashFunc>
struct HTIterator
{
typedef HashNode<T> Node;
typedef HTIterator<K, T, Ptr, Ref, KeyOfT, HashFunc> Self;
typedef HTIterator<K, T, T*, T&, KeyOfT, HashFunc> Iterator;
Node* _node;
const HashTable<K, T, KeyOfT, HashFunc>* _pht;
//HTIterator(Node* node, HashTable<K, T, KeyOfT, HashFunc>* pht)
// :_node(node)
// ,_pht(pht)
// {}
HTIterator(Node* node, const HashTable<K, T, KeyOfT, HashFunc>* pht)
:_node(node)
, _pht(pht)
{}
HTIterator(const Iterator& it)
:_node(it._node)
,_pht(it._pht)
{}
//HTIterator(Node* node, const HashTable<K, T, KeyOfT, HashFunc>* pht)
// :_node(node)
// //, _pht(pht)
//{
// _pht = (HashTable<K, T, KeyOfT, HashFunc>*)pht;
//}
Self& operator++()
{
if (_node->_next)
{
_node = _node->_next;
}
else
{
KeyOfT kot;
HashFunc hf;
size_t hashi = hf(kot(_node->_data)) % _pht->_table.size();
++hashi;
while (hashi < _pht->_table.size())
{
if (_pht->_table[hashi])
{
_node = _pht->_table[hashi];
return *this;
}
++hashi;
}
_node = nullptr;
}
return *this;
}
bool operator!=(const Self& ht)
{
return _node != ht._node;
}
bool operator==(const Self& ht)
{
return _node == ht._node;
}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
};
template<class K, class T, class KeyOfT, class HashFunc = DefaultHashFunc<K>>
class HashTable
{
typedef HashNode<T> Node;
template<class K, class T, class Ptr, class Ref, class KeyOfT, class HashFunc>
friend struct HTIterator;
public:
typedef HTIterator<K, T, T*, T&, KeyOfT, HashFunc> iterator;
typedef HTIterator<K, T, const T*, const T&, KeyOfT, HashFunc> const_iterator;
iterator begin()
{
Node* cur = nullptr;
for (int i = 0; i < _table.size(); i++)
{
if (_table[i])
{
cur = _table[i];
break;
}
}
return iterator(cur, this);
}
iterator end()
{
return iterator(nullptr, this);
}
const_iterator begin() const
{
Node* cur = nullptr;
for (int i = 0; i < _table.size(); i++)
{
if (_table[i])
{
cur = _table[i];
break;
}
}
return const_iterator(cur, this);
}
const_iterator end() const
{
return const_iterator(nullptr, this);
}
HashTable()
:_n(0)
{
_table.resize(10, nullptr);
}
pair<iterator, bool> Insert(const T& data)
{
KeyOfT kot;
iterator it = Find(kot(data));
if (it != end())
{
return make_pair(it, false);
}
HashFunc hf;
if (_n == _table.size())
{
size_t newSize = _table.size() * 2;
vector<Node*> newTable(newSize, nullptr);
for (int i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
while (cur)
{
Node* next = cur->_next;
size_t hashi = hf(kot(cur->_data)) % newTable.size();
cur->_next = newTable[hashi];
newTable[hashi] = cur;
cur = next;
}
_table[i] = nullptr;
}
_table.swap(newTable);
}
size_t hashi = hf(kot(data)) % _table.size();
Node* newnode = new Node(data);
newnode->_next = _table[hashi];
_table[hashi] = newnode;
++_n;
return make_pair(iterator(newnode, this), true);
}
iterator Find(const K& key)
{
HashFunc hf;
KeyOfT kot;
size_t hashi = hf(key) % _table.size();
Node* cur = _table[hashi];
while (cur)
{
if (kot(cur->_data) == key)
{
return iterator(cur, this);
}
cur = cur->_next;
}
return iterator(nullptr, this);
}
bool Erase(const K& key)
{
HashFunc hf;
KeyOfT kot;
size_t hashi = hf(key) % _table.size();
Node* cur = _table[hashi];
Node* prev = nullptr;
while (cur)
{
if (kot(cur->_data) == key)
{
if (prev)
{
prev->_next = cur->_next;
}
else
{
_table[hashi] = cur->_next;
}
delete cur;
cur = nullptr;
_n--;
return true;
}
else
{
prev = cur;
cur = cur->_next;
}
}
return false;
}
~HashTable()
{
for (int i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
while (cur)
{
Node* next = cur->_next;
delete cur;
cur = next;
}
_table[i] = nullptr;
}
}
private:
vector<Node*> _table;
size_t _n;
};
}
unordered_map.h文件
cpp
#pragma once
namespace Sim
{
template<class K,class V>
class unordered_map
{
struct MapKeyOfT
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
public:
typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;
iterator begin()
{
return _ht.begin();
}
iterator end()
{
return _ht.end();
}
const_iterator begin() const
{
return _ht.begin();
}
const_iterator end() const
{
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:
hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT> _ht;
};
}
unordered_set.h文件
cpp
#pragma once
namespace Sim
{
template<class K>
class unordered_set
{
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
public:
typedef typename hash_bucket::HashTable<K, K, SetKeyOfT>::const_iterator iterator;
typedef typename hash_bucket::HashTable<K, K, SetKeyOfT>::const_iterator const_iterator;
const_iterator begin() const
{
return _ht.begin();
}
const_iterator end() const
{
return _ht.end();
}
pair<iterator, bool> insert(const K& key)
{
pair<typename hash_bucket::HashTable<K, K, SetKeyOfT>::iterator, bool> ret = _ht.Insert(key);
return pair<iterator, bool>(ret.first, ret.second);
}
private:
hash_bucket::HashTable<K, K, SetKeyOfT> _ht;
};
}
test.cpp文件
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <vector>
#include <string>
#include <list>
using namespace std;
#include "HashTable.h"
#include "my_unordered_map.h"
#include "my_unordered_set.h"
void test1()
{
Sim::unordered_map<int, int> um;
um.insert(make_pair(1, 1));
um.insert(make_pair(5, 5));
um.insert(make_pair(2, 2));
um.insert(make_pair(3, 3));
Sim::unordered_map<int, int>::iterator umit = um.begin();
while (umit != um.end())
{
cout << umit->first << ":" << umit->second << endl;
++umit;
}
cout << endl;
Sim::unordered_map<string, string> ums;
ums.insert(make_pair("sort", "排序"));
ums.insert(make_pair("insert", "插入"));
ums.insert(make_pair("delete", "删除"));
ums.insert(make_pair("begin", "开始"));
for (auto e : ums)
{
//e.first += 'x';
//e.second += 'x';
cout << e.first << ":" << e.second << endl;
}
cout << endl;
ums["insert"] = "xxx";
ums["proceess"] = "yyyy";
for (auto e : ums)
{
cout << e.first << ":" << e.second << endl;
}
cout << endl;
Sim::unordered_set<int> us;
us.insert(1);
us.insert(5);
us.insert(3);
us.insert(2);
Sim::unordered_set<int>::iterator usit = us.begin();
while (usit != us.end())
{
//*usit = 1;
cout << *usit << " ";
++usit;
}
cout << endl;
}
//void test2()
//{
// pair<int, int> p1(1, 1);
// pair<const int, const int> p2(p1);
// pair<int, int> p3(p2);
//}
//void test3()
//{
// list<int> lt;
// list<int>::iterator it1 = lt.begin();
// list<int>::const_iterator it2 = lt.begin();
//
//}
//
//template<class T,class Ref>
//class A
//{
//public:
// A(Ref a)
// :_a(a)
// {}
// A(const A<T, T&>& a)
// :_a(a._a)
// {}
//
// int _a;
//};
//void test4()
//{
// int x = 1;
// A<int, int&> a(x);
// A<int, const int&> b(a);
//}
int main()
{
test1();
return 0;
}