🪐🪐🪐欢迎来到程序员餐厅💫💫💫
主厨:邪王真眼
主厨的主页:Chef‘s blog
所属专栏:c++大冒险
总有光环在陨落,总有新星在闪烁
前言:
之前我们学完红黑树后对他进行了改造,使之成为map和set的底层容器,今天我们则要把哈希表进行修改并以此为基础实现unordered_set和unordered_map
一.哈希桶(修改版)
1.1.节点
template<class T>
struct HashNode
{HashNode<T> _next;T _data;HashNode(const T& data = T()):_next(nullptr), _data(data){}
};
注意事项:
- 数据类型是T,因为要同时适用unordered_set(存储键值)和unordered_map(存储键值对)
类比咱们用红黑树写map和set时的做法。
1.2.迭代器
哈希表的迭代器类型是单向迭代器,没有反向迭代器
1.2.1成员变量
template<class K, class T, class KeyOfT, class HF>
class HashTable;
template<class K,class T, class Ref,class Ptr,class KeyOfT, class HF>
struct HashIterator
{typedef HashNode<T> Node;typedef HashIterator<K, T, Ref, Ptr, KeyOfT, HF> self;typedef HashIterator<K, T, T&, T*, KeyOfT, HF> iterator;typedef HashTable<K, T, KeyOfT, HF> HT;Node* _node;HT* _ht;
};
注意事项:
- 增加了_ht成员变量,因为在重载++时,当一条单链表走到空,则需要走到下一个哈希桶的位置,需要通过哈希表的vector成员找下一个位置
- 因为HashTable是在后面实现的,所以我们要先写一个声明
1.2.2简单函数实现
HashIterator(Node*&node=nullptr,Node*&ht=nullptr):_node(node),_ht(ht)
{}
HashIterator(const iterator&it):_node(it._node), _ht(it._ht)
{}
Ref& operator*()const
{return _node->_data;
}
Ptr& operator->()const
{return &(_node->_data);
}
bool operator==(const self&se)
{return _node == se._node;
}
bool operator!=(const self& se)
{return _node != se._node;
}
注意事项:
- 拷贝构造函数可以以普通迭代器拷贝出普通迭代器(普通迭代器调用时)以及const迭代器(const迭代器调用时)
1.2.3operator++
self& operator++()
{Node* node = _node;if (node->_next){_node = node->_next;return *this;}else{KeyOfT kot;size_t hash = HF(kot(_node->_data)) % _ht->_tables.size()+1;for (hash; hash < _ht->_tables.size(); hash++){if (_ht->tables[hash]){_node = _ht->tables[hash];return *this;}}_node = nullptr;return *this;}
}
self operator++(int)
{self new_self=*this;(*this)++;return new_self;
}
思路:
- 前置++的思路:
- 下一个结点不为空,则跳到下一位
- 下一个结点为空,则先取模算出哈希地址,再往后探测不为空的哈希桶
- 后置++:复用前置++,返回临时对象
1.3哈希桶本身
1.3.1成员变量
template<class K, class T, class KeyOfT, class HF>
class HashTable
{template<class K, class T, class Ref, class Ptr, class KeyOfT, class HF>friend struct HashIterator;typedef HashNode<T> Node;
public:
protected:vector<Node*> _tables;size_t _size = 0;//有效数据个数
};
注意事项:
- 迭代器要访问哈希桶的私有成员,所以要声明友元
- 模板参数KeyOfT是为了从类型T中取出键值
- 模板参数HF即是HashFunc,哈希函数,用于辅助键值转化为整型进行取模操作
1.3.2构造函数和析构函数
HashTable(size_t size = 10)
{_tables.resize(size);
}
~HashTable()
{for (auto hash_node : _tables){while (hash_node){Node* new_node = hash_node->_next;delete hash_node;hash_node = new_node;}}
}
1.3.3 begin和end
typedef HashIterator< K,T, T&, T*, KeyOfT, HF> iterator;
typedef HashIterator< K, T, const T&, const T*, KeyOfT, HF> const_iterator;
iterator begin()
{for (size_t i = 0; i < _tables.size(); i++)if (_tables[i])return iterator(_tables[i], this);return iterator(nullptr, this);
}
const_iterator begin()const
{for (size_t i = 0; i < _tables.size(); i++)if (_tables[i])return const_iterator(_tables[i], this);return const_iterator(nullptr, this);
}
iterator end()
{return iterator(nullptr, this);
}
const_iterator end()
{return const_iterator(nullptr, this);
}
注意事项:
- begin返回最开始不为空的哈希桶的迭代器,而不是最开始的哈希桶的迭代器
- end返回空迭代器
- 构造迭代器需要传入哈希表本身的地址,这里直接传this指针即可
1.3.4Find函数
iterator Find(const K&key)
{HF hf;KeyOfT kot;size_t hash = hf(key) % _tables.size();Node* cur = _tables[hash];while (cur){if (kot(cur->_data) == key)return iterator(cur,_tables);cur = cur->_next;}return iterator(nullptr,_tables);
}
注意事项:
- 返回值类型是迭代器
- 运用两个仿函数,,kot负责获取存储的键值,hf作为哈希函数把键值key转整型
1.3.5 Insert函数
pair<iterator,bool> Insert(T& data)
{KeyOfT kot;HF hf;iterator it = Find(kot(data));if (it._node){return make_pair(it, false);}if (_size == _tables.size()){vector<Node*> new_tables(_size * 2);for (auto node : _tables){while (node){Node* next = node->_next;size_t hash = hf(kot(node->_data)) % new_tables.size();node->_next = new_tables[hash];new_tables[hash] = node;node = next;}}_tables.swap(new_tables);}size_t hash = hf(kot(data)) % _tables.size();Node* cur = _tables[hash];Node* p(data);p->_next = cur;_tables[hash] = p;_size++;return make_pair(iterator(p,this),true);
}
注意事项:
- 返回值类型是pair,第一个参数为迭代器,第二个参数为布尔值(记录是否插入成功)
- 运用两个仿函数,,kot负责获取存储的键值,hf作为哈希函数把键值key转整型
1.3.6Erase函数
bool Erase(const K& key)
{HF hf;KeyOfT kot;size_t hash = hf(key) % _tables.size();Node* cur = _tables[hash];Node* pre = nullptr;while (cur){if (kot(cur->data) == key)break;pre = cur;cur = cur->_next;}if (cur == nullptr)return false;_size--;if (pre == nullptr)_tables[hash] = cur->_next;elsepre->_next = cur->_next;delete cur;return true;
}
二、unordered_set
2.1成员变量及仿函数
template<class K,class HF=HashFunc<K>>
class unordered_set
{
public:struct SetKeyOfT{K& operator()(const K& key){return key;}};
protected:HashTable<K, K, SetKeyOfT, HF> _hf;
};
注意事项:
- 1.这里的数据存储类型就是键值Key本身,所以SetKeyOfT直接返回key就行
- 2.哈希函数不是固定的,可以根据需求自己进行实现并传到给定模板参数中
2.2 begin和end
typedef typename HashTable<K, K, SetKeyOfT, HF>::iterator iterator;
typedef typename HashTable<K, K, SetKeyOfT, HF>::const_iterator const_iterator;
iterator begin()
{return _ht.begin();
}
const_iterator begin()const
{return _ht.begin();
}
iterator end()
{return _ht.end();
}
const_iterator end()const
{return _ht.end();
}
注意事项:
- 在C++中,编译器默认iterator为变量名,如果作为类型名,需要在前面加上typename加上typename关键字。
- unordered_set中存储的键值key不允许修改,所以其普通迭代器和const迭代器均为哈希表的const迭代器
- 我们称set的begin为A,哈希的begin为B,A的实现是对B的调用,在A的参数是普通迭代器时,B也是普通迭代器,B的返回值也是普通迭代器,但A的返回值是const迭代器,所以这里需要用普通迭代器创建一个const迭代器的临时变量,这就用到之前写的拷贝构造函数了。
2.3Find
iterator Find(const K&key)
{return _ht.Find(key);
}
注意事项:
- 此时也有从普通迭代器到const迭代器的转换
2.4Insert
pair<iterator, bool> Insert(const K& key)
{return _ht.Insert(key);
}
注意事项:
- 函数形参类型是K
- 此时也有从普通迭代器到const迭代器的转换
2.5Erase
bool Erase(const K& key)
{return _ht.Erase(key);
}
三、unordered_map
unordered_map和unordered_set的实现有众多相似之处,
3.1 成员变量与仿函数
template<class K,class V,class HF=HashFunc<K>>
class unordered_map
{struct MapKeyOfT
{const K& operator()(const pair<K, V>&kv){return kv.first;}};
protected:HashTable<K, pair<const K, V>, MapKeyOfT, HF> _ht;
};
注意事项:
- 1.这里节点存的数据类型是pair<K,V>,我们的MapKeyOfT作用是返回其中的键值key
- 2.哈希函数不是固定的,可以根据需求自己进行实现并传到给定模板参数中
3.2 begin和end
typedef typename HashTable<K, pair<K,V>, MapKeyOfT, HF>::iterator iterator;
typedef typename HashTable<K, pair<K,V>, MapKeyOfT, HF>::const_iterator const_iterator;
iterator begin()
{return _ht.begin();
}
const_iterator begin()const
{return _ht.begin();
}
iterator end()
{return _ht.end();
}
const_iterator end()const
{return _ht.end();
}
注意事项:
- 加上typename关键字,编译器才能识别类型
- unordered_map不允许修改key,故普通迭代器和const的pair中的K都要加上const修饰,但是允许修改存储的data,所以普通和const迭代器一一对应(好好想想这点map和set的处理差异)
3.3 find
iterator Find(const K& key)
{return _ht.Find(key);
}
3.4Insert
pair<iterator, bool> Insert(const pair<const K, V>& kv){return _ht.Insert(kv);}
注意事项:
- 形参类型是键值对而不是键值
3.5 operator[ ]
V& operator[](const& K key)
{return (*(_ht.Insert(make_pair(key, V()))).first).second;
}
//或者你也可以这么写
V& operator[](const K& key)
{pair<iterator, bool> cur = insert(make_pair(key, V()));return cur.first->second;
}
注意事项:
- 利用operator[]进行插入和修改操作是很方便的,所以要学会灵活运用哦
- 返回值是value的引用,我们可以直接进行修改
3.6 Erase
bool Erase(const K& key)
{return _ht.Erase(key);
}
如果你对该系列文章有兴趣的话不妨点个关注哦,我会持续更新相关博客的
🥰创作不易,你的支持对我最大的鼓励🥰
🪐~ 点赞收藏+关注 ~🪐