文章目录
- (1)修改原哈希表
- (2)迭代器
- (3)最后一步
- (4)关于key是自定义类型的额外补充(面试题)
- (5)源代码
(1)修改原哈希表
和红黑树封装一样的逻辑,unordered_map/set公用一张哈希表,所以改的时候两个容器都要一块取考虑,因为需要通过泛型去设计,要有复用思想。
这里一部分就简单说一下,比如第二个参数是K就是K,为什么要传第一个模板参数,它要和map复用,因为map也需要用到key,第一个模板参数拿到K的类型,因为查找,删除等接口的参数是K,第二个模板参数决定了树的节点存什么,是K还是KV。
我们一步一步加并修改原来哈希表的内容,这里就不叫V,自己写的时候这叫T,把节点结构体里面的pair类型改成T,构造函数里面用data初始化_data。以及后面涉及到pair<K,V>&kv的全都改成T&data。unordered_map就通过第二个模板参数控制node里面的data,同理unordered_set也一样。
之前是kv现在要改为_data,把kv.first改为_data,但是这里的第二个_data并不是我们想要的,因为这里的data到底是什么并不知道,上一层加了一层泛型。这需要用仿函数解决Key。原来哈希表里面涉及到hash(cur->kv.first) 需要成hash(kot(cur->_data)),表示通过指针获取节点的_data数据,我们并不知道这是什么类型需要再用仿函数Key创建的对象kot调用()来获取具体的数据,最后通过hash()来获取键值所对应的存储位置。
(2)迭代器
如上图,对于++it怎么走,大致思路是这样的,如果node不等于空继续往下走,如果为空,就需要找下一个桶,
这里写迭代器的时候因为++需要用到哈希表,就需要前置声明,这里存在互相引用的问题,迭代器需要哈希表,哈希表需要迭代器,就要把哈希表定义在前面,其他的一样的老套路,只不过里面多了一个哈希表指针,构造的时候需要初始化,另外另一个构造函数一会实现,主要这里先把迭代器++实现出来,因为它的迭代器只支持单向,没有减减。
实现迭代器加加解析:
如果node的下一个指针不为空,就继续往下走更新_node,如果为空,可能走到尾部了,就需要找下一个桶,下一个不为空的桶在哪我们不知道,先算自己在哪,先把_data里面的key取出来,再用哈希的仿函数获得整数,模上表的大小找到映射位置,算出来hashi加加一下,就去找不为空的桶,如果hashi小于数据个数,就进如循环,再判断如果下一个位置不为空,就把表的第一个节点指针给_node,否则++hashi,就过走完了整个表,没有找到下一个位置,就用空取替带_node。最后返回this指针。如下图代码:
Self& operator++(){if (_node->_next != nullptr){_node = _node->_next;}else{Key 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;}
我们实现begin和end,begin:要返回第一个桶的第一个数据,这个begin不是直接构建的,需要遍历去找第一个不为空的桶,找到之后就构建迭代器,但是除了传找到的这个节点,还得传哈希表,我们直接可以用this指针,就是指向哈希表对象本身。
end:就直接拿空就构造就可以
同理const_begin和const_end同理
iterator begin(){Node* cur = nullptr;for (size_t i = 0; i < _tables.size(); ++i){cur = _tables[i];if (cur){break;}}return iterator(cur, this);}
上面需要一些细节,迭代器需要访问_table的私有,就让我们这个哈希表这个类提供一个get.size和get.table,也可以写一个友元函数,因为是模板还得需要加上模板参数,如图:
我们继续在unordered_map增加迭代器参数,同理unordered_set也一样,在哈希表类里里面曾加一个普通迭代器和const迭代器,如图下图:
注意这里的unordered_set两个普通迭代器底层用的都是const迭代器,unordered_map普通迭代器用的普通迭代器,因为它需要修改它的Value,它的K不能修改是通过paie里面的constkey达到的,而unordered_set只有K,K是不是能修改的。
但是直接用会报错,因为unordered_set调用begin的时候,普通对象调用的普通begin,返回的是普通迭代器,但这里是const迭代器,这里就不具体演示了,和红黑树封装一样的逻辑。具体原因是我们迭代器里没有写对应的构造,需要一个支持普通到const迭代器的构造,我们在里面单独加一个普通迭代器Iterator,构造里它的参数前面加一个const,具体就不说了可以看红黑树封装。其实就是权限的缩小。这里还有一个细节,如果调用const_iterator会有问题,因为begin是const,返回时,迭代器里的this指针就是const的,这时候要要把迭代器构造的哈希表对象指针改成const的。因为迭代器里面不会修改这里的哈希表,需要取遍历它,传普通传const都可以接收,因为权限可以缩小。
(3)最后一步
我们在unordered_map里面提供一个[],它要去访问第二个V值也就是pair的second,如果它没有就插入,如果有就返回key所在节点的迭代器,因为里面调用的是insert,这样就不仅能查找对应的value值,并有修改功能并可以插入新的键值对,insert返回值得个是个pair类型。而且并把有关的insert返回值,如果返回成功就返回新插入节点的构造的迭代器加一个true即make_pair(iterator(newnode, this), true),同样unordered_set的insert也一样改为pair<iterator,bool>,里面是是一个迭代器和布尔值。
总之,insert返回值是个pair类型,find返回的是一个迭代器,erase返回bool值,unordered_set除了不提供[],其他都一样。
另外,我们的key还可以支持自定义类型,但是需要要满足支持转换成取模的仿函数,和等于比较和等于比较,调用unordered_map/set需要显示传HashDate仿函数如图:
如下图我们进行的测试和结果:
(4)关于key是自定义类型的额外补充(面试题)
1、一个类型要做unordered哈希系列的key:要满足支持转换成取模的仿函数和等于比较,如上。
2、一个类型要做map系列的key:要满足支持<比较。
在STL源码库中,map默认的第三个参数为less<_Key>,就是将key按照从小到大的顺序排列,其内部实现很简单,就是直接比较两个key的类型返回。但是如果是自定义类型的时候,直接用默认的less比较的话会报错,因为编译器不知道怎么处理,即不知道如何比较他们的大小,它能应对内置类型比如int等,比如复合类型string。如何解决?2种方法:
第一种方法:继续使用第三个模板参数,因为第三个模板参数内部是直接比较key的类型大小,我们可以在自定义的类型里实现重载<运算符,让它知道如何比较这两个key,因为默认参数是less,所以只需要重载<运算符就可以了,如果参数是greater,那么就需要重载>运算符。
struct nza {nza(int n1 = 0, int n2 = 1):_n1(n1), _n2(n2) {}bool operator<(const nza& Data) const{return _n1+_n2 < Data._n1 + Data._n2;}int _n1;int _n2;
};int main() {map<nza, int> myMap;}
第二种方法就是:自己实现一个仿函数。
struct nza {nza(int n1 = 0, int n2 = 1):_n1(n1), _n2(n2) {}int _n1;int _n2;
};
template<class T>
struct RLcmp {bool operator()(const T& Data1, const T& Data2)const {return Data1._n1 + Data1._n2< Data1._n1 + Data2._n2;}
};int main()
{map<nza, int, RLcmp<nza> > myMap;
}
(5)源代码
OpenHash.h
#pragma once
#include<utility>
#include<vector>
#include<string>
#include<iostream>
using namespace std;template<class K>
struct HashFunc
{size_t operator()(const K& key){return key;}};
template<>
struct HashFunc<string>
{size_t operator()(const string& str){size_t hash = 0;for (auto& e : str){hash += e;hash *= 131;}return hash;}};template<class T>struct HashNode{T _data;HashNode<T>* _next;HashNode(const T& data):_next(nullptr), _data(data){}};template<class K, class T, class Key, class Hash>class HashTable;template<class K, class T, class Ref, class Ptr, class Key, class Hash>struct _HashIterator{typedef HashNode<T> Node;typedef HashTable<K, T, Key, Hash> HT;typedef _HashIterator<K, T, Ref, Ptr, Key, Hash> Self;typedef _HashIterator<K, T, T&, T*, Key, Hash> Iterator;Node* _node;const HT* _ht;_HashIterator(Node* node, const HT* ht):_node(node), _ht(ht){}_HashIterator(const Iterator& it):_node(it._node), _ht(it._ht){}Ptr operator->(){return &_node->_data;}Ref operator*(){return _node->_data;}bool operator!=(const Self& s){return _node != s._node;}bool operator==(const Self& s) const{return _node == s._node;}Self& operator++(){if (_node->_next != nullptr){_node = _node->_next;}else{Key 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 Key, class Hash>class HashTable{template<class K, class T, class Ref, class Ptr, class Key, class Hash>friend struct _HashIterator;typedef HashNode<T> Node;public:typedef _HashIterator<K, T, T&, T*, Key, Hash> iterator;typedef _HashIterator<K, T, const T&, const T*, Key, Hash> const_iterator;iterator begin(){Node* cur = nullptr;for (size_t i = 0; i < _tables.size(); ++i){cur = _tables[i];if (cur){break;}}return iterator(cur, this);}iterator end(){return iterator(nullptr, this);}const_iterator begin()const{Node* cur = nullptr;for (size_t i = 0; i < _tables.size(); ++i){cur = _tables[i];if (cur){break;}}return const_iterator(cur, this);}const_iterator end()const{return const_iterator(nullptr, this);}~HashTable(){for (auto& cur : _tables){while (cur){Node* next = cur->_next;delete cur;cur = next;}cur = nullptr;}}pair<iterator, bool> Insert(const T& data){Key kot;iterator it = Find(kot(data));if (it != end()){return make_pair(it, false);}Hash hash;if (_n == _tables.size()){/*size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;*/size_t newsize = GetNextPrime(_tables.size());vector<Node*> newtables(newsize, nullptr);for (auto& cur : _tables){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.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){Key kot;if (_tables.size() == 0)return end();Hash hash;size_t hashi = hash(key) % _tables.size();Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == key){return iterator(cur, this);}cur = cur->_next;}return end();}bool Erase(const K& key){Key kot;Hash hash;size_t hashi = hash(key) % _tables.size();Node* prev = nullptr;Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == key){if (prev == nullptr){_tables[hashi] = cur->_next;}else{prev->_next = cur->_next;}delete cur;return true;}else{prev = cur;cur = cur->_next;}}return false;}size_t GetNextPrime(size_t prime){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};size_t i = 0;for (; i < __stl_num_primes; ++i){if (__stl_prime_list[i] > prime)return __stl_prime_list[i];}return __stl_prime_list[i];}private:vector<Node*> _tables;size_t _n = 0;};
unordered_Map.h
#pragma once
#include"OpenHash.h"
namespace nza
{template<class K, class V,class Hash=HashFunc<K>>class unordered_Map{public:struct MapKey{const K& operator()(const pair<K,V>& kv){return kv.first;}};public:typedef typename HashTable<K, pair<const K,V>, MapKey,Hash>::iterator iterator;typedef typename HashTable<K, pair<const K,V>, MapKey,Hash>::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();}V& operator[](const K& key){pair<iterator, bool> ret = insert(make_pair(key, V()));return ret.first->second;}pair<iterator, bool> insert(const pair<K, V>& kv){return _ht.Insert(kv);}iterator find(const K& key){return _ht.Find(key);}bool erase(const K& key){return _ht.Erase(key);}private:HashTable<K,pair<const K, V>, MapKey,Hash> _ht;};class Date{friend struct HashDate;public:Date(int year = 2000, int month = 12, int day = 23): _year(year), _month(month), _day(day){}bool operator<(const Date& d)const{return (_year < d._year) ||(_year == d._year && _month < d._month) ||(_year == d._year && _month == d._month && _day < d._day);}bool operator>(const Date& d)const{return (_year > d._year) ||(_year == d._year && _month > d._month) ||(_year == d._year && _month == d._month && _day > d._day);}bool operator==(const Date& d) const{return _year == d._year&& _month == d._month&& _day == d._day;}friend ostream& operator<<(ostream& _cout, const Date& d);private:int _year;int _month;int _day;};ostream& operator<<(ostream& _cout, const Date& d){_cout << d._year << "-" << d._month << "-" << d._day;return _cout;}struct HashDate{size_t operator()(const Date& d){size_t hash = 0;hash += d._year;hash *= 31;hash += d._month;hash *= 31;hash += d._day;hash *= 31;return hash;}};void TestM1(){unordered_Map<int, int> m;m.insert(make_pair(66, 66));m.insert(make_pair(77, 77));m.insert(make_pair(160, 160));unordered_Map<int, int>::iterator it = m.begin();while (it != m.end()){cout << it->first << ":" << it->second << endl;++it;}cout << endl;}void TestM2(){Date d1(2016, 3, 15);Date d2(2016, 3, 15);Date d3(2016, 3, 12);Date d4(2016, 3, 11);Date d5(2023, 3, 12);Date d6(2023, 3, 13);Date a[] = { d1, d2, d3, d4, d5, d6 };unordered_Map<Date, int, HashDate> cm;for (auto e : a){cm[e]++;}for (auto& kv : cm){cout << kv.first << ":" << kv.second << endl;}cout << endl;}
}
unordered_Set.h
#pragma once
#include"OpenHash.h"
namespace nza
{template<class K,class Hash=HashFunc<K>>class unordered_Set{public:struct SetKey{const K& operator()(const K& key){return key;}};public:typedef typename HashTable< K, K, SetKey,Hash>::const_iterator iterator;typedef typename HashTable< K, K, SetKey,Hash>::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 K& key){return _ht.Insert(key);}iterator find(const K& key){return _ht.Find(key);}bool erase(const K& key){return _ht.Erase(key);}private:HashTable<K, K, SetKey,Hash> _ht;};void TestS1(){int a[] = { 1, 6, 4, 66, 160, 32, 44 };unordered_Set<int> s;for (auto e : a){s.insert(e);}s.insert(35);s.insert(117);unordered_Set<int>::iterator it = s.begin();while (it != s.end()){cout << *it << " ";++it;}cout << endl;for (auto e : s){cout << e << " ";}cout << endl;}
}
test.cpp
#include"OpenHash.h"
#include"unordered_Set.h"
#include"unordered_Map.h"int main()
{nza::TestM1();nza::TestM2();nza::TestS1();}