本期我们来封装实现unordered系列,需要前置知识,没有看过哈希的建议先看看哈希,而且哈希的代码都在这里面,一会要用到
C++-哈希Hash-CSDN博客
目录
代码实现
迭代器
const迭代器
全部代码
代码实现
首先我们要把V改为T,因为我们不知道他是k结构还是kv结构
//unordered_set.h
namespace bai {
template<class K>
class unordered_set
{
private:
hash_bucket::HashTable<K, K> _ht;
};
}
namespace bai {
template<class K,class V>
class unordered_map
{
private:
hash_bucket::HashTable<K,pair<K,V>> _ht;
};
}
然后我们把框架搭好
我们对照着看一下,如果是set,这里就是key,data就是key,如果是map,这里就是pair,data就是pair
接下来还要修改insert,find等等,这里就因为不知道是pair还是k的原因,所以我们需要再次来写KeyOfT
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
struct MapKeyOfT
{
const K& operator()(const pair<K,V>& kv)
{
return kv.first;
}
};
写完后别忘记加上
bool Insert(const T& data)
{
KeyOfT kot;
if (Find(kot(data)))
{
return false;
}
HashFunc hf;
//负载因子到1时扩容
if (_n == _table.size())
{
size_t newSize = _table.size() * 2;
vector<Node*> newTable;
newTable.resize(newSize, nullptr);
//遍历旧表,把节点迁下来挂到新表
for (size_t i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
while (cur)
{
Node* next = cur->_next;
//头插到新表
size_t hashi = hf(kot(cur->_data)) % newSize;
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;
}
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;
}
bool Erase(const K& key)
{
HashFunc hf;
KeyOfT kot;
size_t hashi = hf(key) % _table.size();
Node* prev = nullptr;
Node* cur = _table[hashi];
while (cur)
{
if (kot(cur->_data) == key)
{
if (prev == nullptr)
{
//头删
_table[hashi] = cur->_next;
}
else
{
prev->_next = cur->_next;
}
delete cur;
return true;
}
cur = cur->_next;
}
}
接着我们要修改insert,find和erase的代码,下面我们来看看修改了哪里,我们按顺序看
大概就是这些地方,具体的还要看上面的代码
我们简单测试一下,没有问题,下面我们来封装迭代器
迭代器
要实现迭代器,我们要想想,当前桶走完了,如何到下一个桶呢?也就是2号位的桶完了后如何到6号位
template<class T>
struct HTiterator
{
typedef HashNode<T> Node;
Node* _node;
HTiterator(Node* node)
:_node(node)
{}
operator++()
{
if (_node->_next)
{
_node = _node->next;
}
else
{
}
}
};
我们先把框架写好,我们来看operator++,如果当前桶没有走完,就让他到下一个位置即可
如果我们知道当前位置是几号桶可以解决吗?也不行,因为我们还需要哈希表的对象,大家想一想,他的底层是一个指针数组,每一个指针又指向一个单链表,我们现在是需要在指针数组里移动
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;
}
else
{
++hashi;
}
}
_node = nullptr;
}
return *this;
}
};
所以我们需要把整个哈希表传过来,传一个pht的指针,而且这里和之前一样,不一定是可以直接取模,需要再套一层hashfunc,此时我们就可以计算出当前是第几个桶
接着我们开始找下一个不为空的桶,我们先对hashi++,从下一个位置开始寻找,如果当前桶不为空,我们就break,否则++hashi,循环结束后是有两种情况的,一种是可以找到,我们要让it指向下一个桶,另一种是我们已经找完了整个表,已经走到结尾了,我们让nullptr充当end的位置
另外这里可能会有人有疑问,为什么这里没有加typename? 只有不能区分静态变量和类型时才需要加
T& operator*()
{
return _node->_data;
}
T* operator->()
{
return &_node->_data;
}
bool operator!=(const Self& s)
{
return _node != s._node;
}
bool operator==(const Self& s)
{
return _node == s._node;
}
我们顺手再把这几个也写一下
typedef HTiterator<K, T, KeyOfT, HashFunc> iterator;
iterator begin()
{
//找第一个桶
for (size_t i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
if (cur)
{
return iterator(cur,this);
}
}
return iterator(nullptr, this);
}
iterator end()
{
return iterator(nullptr, this);
}
接着我们在HashTable里加上begin和end,我们构造迭代器时第一个传cur,找不到传nullptr,第二个传this即可,this就是哈希表的指针
下面我们再套一层,在unordered里封装一层
注意,这里就需要加typename了,我们之前封装map和set时也是一样的
下面我们要开始解决各种问题了
这里是一个相互依赖的问题
首先我们在哈希表里调用迭代器 ,迭代器在前,没有问题
但是我们在迭代器里又用了哈希表,这里谁在前谁在后都不行,不过还好这里用的是哈希表的指针,如果是对象就不好解决了
这里我们就要用到前置声明了,告诉编译器哈希表我们是有定义的,只不过可能在后面,先不要着急
此时问题就解决了
我们调用一下迭代器,再次编译,出现了这样的错误,我们仔细看可以发现,这里是私有的问题
这里在类外边是不能访问私有的 ,所以这里可以写一个gettable,或者使用友元
是迭代器要访问哈希表,所以迭代器是哈希表的友元,类模板的友元需要带上模板参数
此时我们的迭代器就可以跑起来了
接下来我们需要解决可以修改的问题,也就是要实现const迭代器了
const迭代器
和之前一样,要实现const迭代器我们需要传T*和T&
于是我们修改迭代器的模板参数和self
operator*和箭头的返回参数也要修改
友元声明也需要修改
const_iterator begin() const
{
//找第一个桶
for (size_t i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
if (cur)
{
return const_iterator(cur, this);
}
}
return const_iterator(nullptr, this);
}
const_iterator end() const
{
return const_iterator(nullptr, this);
}
然后要在HashTable里加上begin和end
接着我们对外层的set封装,无论是iterator还是const_iterator,它们其实都是const_iterator,而且只提供了const版本的begin和end,和库里面是一样的,这里的begin和end的返回值无论是const_iterator还是iterator都是可以的,因为普通对象是可以调用const成员函数的
此时我们的第一个问题就解决了
接着我们编译,发现这里编译不通过,不仅仅是这里,上面也是编译不通过的,这里报错是无构造函数可以接受源类型
我们来看,这里的const修饰的谁?
修饰的是*this
所以这里this的类型应该是这样的
我们画出来就是这样的,这里权限放大,是传不过去的 ,我们之前写的const迭代器没有遇到这样的问题,因为以前的const迭代器是不需要传哈希表的,而只需要传一个节点的指针过去即可
我们来看这种方法,我们重载了一个构造函数,并且直接把_pht改为了const
对于原来的构造函数,pht是const变为非const,是权限的缩小
那此时我们不要第一个构造,只要第二个可以吗?
也是可以的,普通迭代器传过来只有const,权限缩小
我们来看库里面是怎么解决的,首先是普通的迭代器
这里有一个
库里面写了两个迭代器,用两个迭代器类来实现
这里还都写成const了,相当于单独写了一个类来解决
我们的方法和库里面的方法,这两种方法都可以解决
map的话和以前一样,改为const K
修改这些地方
这样就解决了
下面我们来修改返回值
先修改一下find的返回值
然后是insert的
然后我们把map和set封装的insert也改一下,然后就是set这里的问题了
这里看起来是这样的
但是我们看这里
但是它们是不同区域的代码
set不允许修改key,所以选择用const迭代器替代普通迭代器
所以这里的两个pair是不一样的,pair是一个类模板,实例化为两个不一样的类,是不一样的类型
也就是说上面的pair是这样的,所以会报错
map为什么没报错?
因为map的iterator就是iterator
注意,set这里的问题和权限缩小之类无关,只有指针和引用才有权限缩小的概念,这里是类模板实例化不同的模板参数,导致它们是不同的类型
我们之前在封装map和set就讲过了,这是库里面list的实现办法,不太清楚的各位可以去我往期的map和set实现里再看一看,我把链接放在下面
map和set模拟实现-CSDN博客
下面这个圈主的函数,如果是普通迭代器,这里就是拷贝构造,是const迭代器时,是构造函数,是支持普通迭代器转换成const迭代器的构造,这是一个有双重意义的函数
我们来用这个举例子,我们想用aa1构造aa3
我们要加这样一个构造,但是这里报错了一个私有
原因是上面这里是A<T,Ref>
这里是A<T,T&>
当这里Ref也是T&时是同一个类,当Ref是const时就不是同一个类了
我们看为什么库里面不报错呢?
因为库里面是struct
我们去掉private就好了,这些都是细节,大家需要注意
现在回到我们的哈希表里
我们也加上这样一个构造
接着我们再次编译,发现了几个错误
首先是我们把这里改为it(这里之前是写错了)
然后是find返回时不能返回nullptr,这里返回end(),接着编译就可以通过了
我们上次在这里写的和这里是不一样的
这里我们是先接收pair,再用pair去调用它的构造
这样写是没问题的,这里相当于把两个参数提取了出来,单独调用pair构造,这里的ret.first是一个普通迭代器,在pair的初始化列表里,初始化列表对自定义类型会调用构造
而这里屏蔽的(我们之前写的),是编译器的不严格检查,如果检查的严格一点,是不通过的
首先这里调用pair的构造
我们上面的a<int>和a<const int>都不是同一个类型,这里也是一样的
所以我们把iterator取出来
然后直接调用它的构造,就会走它的初始化列表,初始化列表对于自定义类型就会调用它的构造
虽然这里两种方法都可以编译通过,但是我们还是建议使用方法2,不然换一个编译器可能就不通过了
此时我们的问题就解决了
我们上面那么多事情,都是因为它
insert要返回pair,就引发了普通迭代器转换const迭代器的问题,为什么insert要返回pair呢?
因为我们要实现operator[ ] ,这里都是相关联的
V& operator[](const K& key)
{
pair<iterator,bool> ret = _ht.Insert(make_pair(key, V()));
return ret.first->second;
}
为什么我们这里接收时可以用iterator?
因为map的iterator就是iterator,和set不一样
此时我们就可以使用[ ] 了,和库里面一样
全部代码
#include"Hash.h"
//unordered_set.h
namespace bai {
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<const_iterator, bool> insert(const K& key)
{
//return _ht.Insert(key);
pair<typename hash_bucket::HashTable<K, K, SetKeyOfT>::iterator, bool> ret = _ht.Insert(key);
return pair<const_iterator, bool>(ret.first, ret.second);
}
private:
hash_bucket::HashTable<K, K, SetKeyOfT> _ht;
};
}
#include"Hash.h"
//unordered_map.h
namespace bai {
template<class K,class V>
class unordered_map
{
struct MapKeyOfT
{
const K& operator()(const pair<const 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;
};
}
#pragma once
#include<vector>
#include<iostream>
using namespace std;
//Hash.h
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)
{
//BKDR
size_t hash = 0;
for (auto ch : str)
{
hash *= 131;
hash += ch;
}
return hash;
}
};
namespace open_address {
enum STATE //状态
{
EXIST,
EMPTY,
DELETE
};
template<class K, class V>
struct HashData
{
pair<K, V> _kv;
STATE _state = EMPTY;
};
//struct stringHashFunc
//{
// size_t operator()(const string& str)
// {
// return str[0];
// }
//};
template<class K, class V, class HashFunc = DefaultHashFunc<K>>
class HashTable
{
public:
HashTable()
{
_table.resize(10);
}
bool Insert(const pair<K, V>& kv)
{
if(Find(kv.first))
{
return false;
}
//扩容
//if ((double)_n / _table.size() >= 0.7)
if (_n * 10 / _table.size() >= 7)
{
size_t newSize = _table.size() * 2;
//重新映射
HashTable<K, V, HashFunc> newHT;
newHT._table.resize(newSize);
for (size_t i = 0; i < _table.size(); i++)
{
if (_table[i]._state == EXIST)
{
newHT.Insert(_table[i]._kv);
}
}
_table.swap(newHT._table);
}
//线性探测
HashFunc hf;
size_t hashi = hf(kv.first) % _table.size();
while (_table[hashi]._state == EXIST)
{
++hashi;
hashi %= _table.size();//防止越界,这样可以回到数组开头
}
_table[hashi]._kv = kv;
_table[hashi]._state = EXIST;
++_n;
return true;
}
HashData<const K, V>* Find(const K& key)
{
//线性探测
HashFunc hf;
size_t hashi = hf(key) % _table.size();
while (_table[hashi]._state != EMPTY)
{
if (_table[hashi]._state == EXIST
&& _table[hashi]._kv.first == key)
{
return (HashData<const K, V>*) & _table[hashi];
}
++hashi;
hashi %= _table.size();
}
return nullptr;
}
bool Erase(const K& key)
{
HashData<const K, V>* ret = Find(key);
if (ret)
{
ret->_state = DELETE;
--_n;
return true;
}
return false;
}
private:
vector<HashData<K, V>> _table;
size_t _n; //存储有效数据的个数
};
}
namespace hash_bucket
{
template<class T>
struct HashNode
{
T _data;
HashNode<T>* _next;
HashNode(const T& data)
:_data(data)
,_next(nullptr)
{}
};
//前置声明
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)
{}
//普通迭代器时是拷贝构造
//cosnt迭代器时是构造
HTiterator(const Iterator& it)
:_node(it._node)
, _pht(it._pht)
{}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
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;
}
else
{
++hashi;
}
}
_node = nullptr;
}
return *this;
}
bool operator!=(const Self& s)
{
return _node != s._node;
}
bool operator==(const Self& s)
{
return _node == s._node;
}
};
//set -> hash_bucket::HashTable<K, K> _ht;
//map -> hash_bucket::HashTable<K, pair<K,V>> _ht;
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()
{
//找第一个桶
for (size_t i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
if (cur)
{
return iterator(cur,this);
}
}
return iterator(nullptr, this);
}
iterator end()
{
return iterator(nullptr, this);
}
const_iterator begin() const
{
//找第一个桶
for (size_t i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
if (cur)
{
return const_iterator(cur, this);
}
}
return const_iterator(nullptr, this);
}
const_iterator end() const
{
return const_iterator(nullptr, this);
}
HashTable()
{
_table.resize(10,nullptr);
}
~HashTable()
{
for (size_t i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
while (cur)
{
Node* next = cur->_next;
delete cur;
cur = next;
}
_table[i] = 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;
//负载因子到1时扩容
if (_n == _table.size())
{
size_t newSize = _table.size() * 2;
vector<Node*> newTable;
newTable.resize(newSize, nullptr);
//遍历旧表,把节点迁下来挂到新表
for (size_t i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
while (cur)
{
Node* next = cur->_next;
//头插到新表
size_t hashi = hf(kot(cur->_data)) % newSize;
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 end();
}
bool Erase(const K& key)
{
HashFunc hf;
KeyOfT kot;
size_t hashi = hf(key) % _table.size();
Node* prev = nullptr;
Node* cur = _table[hashi];
while (cur)
{
if (kot(cur->_data) == key)
{
if (prev == nullptr)
{
//头删
_table[hashi] = cur->_next;
}
else
{
prev->_next = cur->_next;
}
delete cur;
return true;
}
prev = cur;
cur = cur->_next;
}
--_n;
return false;
}
void Print()
{
for (size_t i = 0; i < _table.size(); i++)
{
printf("[%d]->", i);
Node* cur = _table[i];
while (cur)
{
cout << cur->_kv.first << ":" <<cur->_kv.second <<"->";
cur = cur->_next;
}
printf("NULL\n");
}
cout << endl;
}
private:
vector<Node*> _table; //指针数组
size_t _n = 0; //有效数据
};
}
以上即为本期全部内容,希望大家可以有所收获
如有错误,还请指正