前言
再看此篇前,建议实现看我文章前面的 “红黑树模拟实现map/set” 和 “哈希表的模拟实现”,这样再看本篇文章时会更加的好理解。
一 如何实现对unordered_map/set的管理
1.1 链地址法的概念
开放定址法中所有的元素都放到哈希表⾥,链地址法中所有的数据不再直接存储在哈希表中,哈希表中存储⼀个指针,没有数据映射这个位置时,这个指针为空,有多个数据映射到这个位置时,我们把这些冲突的数据链接成⼀个链表,挂在哈希表这个位置下⾯,链地址法也叫做拉链法或者哈希桶。就是类似这样的,应该也是好理解的。1.2 哈希表的改造
unordered_map/set的实现是基于哈希表来实现的,那么我们就以哈希表为基础来进行改造来实现我们的unordered_map/set。
1.2.1 哈希链表结构体的定义
template<class K,class V> struct HashNode { pair<K, V> _kv; HashNode<K,V>* _next; HashNode(const pair<K,V>& kv) :_kv(kv) ,_next(nullptr) { } };
首先我们可以用pair结构来存储key对应的value值,然后再设置一个_next指针一个个往下链接,这样就能对哈希链表链接的基本实现了。
1.2.2 哈希表内的私有成员
template<class K,class V,class Hash = HashFunc<K>> class HashTable { private: //vector<list<pair<K, V>>> _tables; vector<Node*> _tables; // 指针数组 size_t _n = 0; };
定义一个存节点的vector数组_tables和_n来记录当前哈希表存了多少的数据
1.2.3 哈希表的初始化
HashTable(size_t size = __stl_next_prime(0)) :_tables(size,nullptr) { } ~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; } }
代码中__stl_next_prime(0)在上一节哈希表的实现已经说过了,就是用来取数据来决定给tables开多少的空间,初始默认参数给0就是size为默认的53,然后tables里面初始化是没有任何数据的,给个默认的nullptr就可以了。然后析构函数,由于在哈希桶里,数据有的是一个桶里面是挂着多个数据,有的甚至没有数据的,那么只需要做一个for循环遍历tables,里面设置一个cur,然后看cur是否为空,不为空就delete这个节点然后往后走,直到为空后再把tables这个地方设置成nullptr,这样就是完成析构了。
1.2.4 迭代器的实现
1迭代器的的结构
template<class T> struct HashNode { T _data; HashNode<T>* _next; HashNode(const T& data) :_data(data) ,_next(nullptr) { } };
首先一个类模板T,然后定义一个HashNode结构体,里面T类型的data和下一个节点的地址,这样一个迭代器结构就实现了。
2 构造
//前置声明 template<class K,class T,class KeyOfT,class Hash> class HashTable; template<class K,class T,class Ref,class Ptr,class KeyOfT, class Hash> struct HTIterator { typedef HashNode<T> Node; typedef HashTable<K, T, KeyOfT, Hash> HT; typedef HTIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self; Node* _node; const HT* _ht; HTIterator(Node* node,const HT* ht) :_node(node) ,_ht(ht) { } };
HashTable就是上一节实现的哈希表,他是写在了迭代器的后面,所以我们就得先声明才能对HashTable的使用,否则编译器会不认识它,那这时候就有人问了,为什么不把它放在HashTable的后面呢?我们要知道,HashTable改造后也是需要加入迭代器的,写在后面那不还是要声明,而且会比这样更麻烦些,所以这样就是最好的实现办法,然后再迭代器部分,我们需要的参数K(key),T(value),Ref(T&),Ptr(*),KeyOfT(仿函数),Hash(对类型处理的方法)。接着就是再迭代器内部首先定义是迭代器的节点,然后再定义一个哈希表,然后构造函数里面便是节点和哈希表的初始化。
3 * ->
Ref operator*() { return _node->_data; } Ptr operator->() { return &_node->_data; }
4 ++
Self& operator++() { if (_node->_next) { //当前桶向前走 _node = _node->_next; } else { //找下一个桶的第一个节点 KeyOfT kot; Hash hs; size_t hashi = hs(kot(_node->_data)) % _ht->_tables.size(); ++hashi; while (hashi < _ht->_tables.size()) { if (_ht->_tables[hashi]) { _node = _ht->_tables[hashi]; break; } else { ++hashi; } } //所有桶都走完了,nullptr去做end() if (hashi == _ht->_tables.size()) _node = nullptr; } return *this; }
++的实现就是先看节点的next是否为空,不为空就让node等于下一个数据就可以了,为空那就先找下一个桶的第一个节点,首先计算出当前节点的hashi,然后先++,如果还是空就继续++,不为空那就可以让node等于hashi位置的第一个数据的位置,然后break结束当前循环,如果一直没找到,走完循环了,那就直接给nullptr当最后一个节点。(这里有可能会有_ht的成员没办法访问的问题,只需要把HTIterator设置成HashTable的友元函数就可以了)
5 != ==
bool operator!=(const Self& s) { return _node != s._node; } bool operator==(const Self& s) { return _node == s._node; }
!=和==没什么好说的,挺容易理解的。
1.2.5 Begin和End
Iterator Begin() { // 找第一个不为空的桶里面的第一个节点 for (size_t i = 0; i < _tables.size(); i++) { if (_tables[i]) { return Iterator(_tables[i], this); } } return End(); } Iterator End() { return Iterator(nullptr,this); } ConstIterator Begin() const { // 找第一个不为空的桶里面的第一个节点 for (size_t i = 0; i < _tables.size(); i++) { if (_tables[i]) { return ConstIterator(_tables[i], this); } } return End(); } ConstIterator End() const { return ConstIterator(nullptr,this); }
迭代器实现一个普通和一个const版本迭代器,这两个都没什么差别,多个const罢了,begin这里说一下,首先要找第一个数据的位置,直接写一个for循环遍历找,找到一个_tables[i]不为空,就直接返回这个迭代器,他就是第一个位置,如果一个都没找到,那就是一个空的哈希表。直接给一个nullptr就可以了。
1.2.6 插入
pair<Iterator,bool> Insert(const T& data) { KeyOfT kot; Iterator it = Find(kot(data)); if (it != End()) return { it,false }; Hash hs; //负载因子到1,在扩容 if (_n == _tables.size()) { // 也可以,但是扩容新开辟节点,释放旧节点,有点浪费 /*HashTable<K, V> newHT(__stl_next_prime(_tables.size() + 1)); for (size_t i = 0; i < _tables.size(); i++) { Node* cur = _tables[i]; while (cur) { newHT.Insert(cur->_kv); cur = cur->_next; } } _tables.swap(newHT._tables);*/ vector<Node*> newtables(__stl_next_prime(_tables.size() + 1)); for (size_t i = 0; i < _tables.size(); i++) { Node* cur = _tables[i]; while (cur) { // 旧表的节点挪动下来 // 插入到映射的新表位置 Node* next = cur->_next; size_t hashi = hs(kot(cur->_data)) % _tables.size(); cur->_next = newtables[hashi];//想象成newnode 头插 newtables[hashi] = cur; cur = next; } _tables[i] = nullptr; } _tables.swap(newtables); } size_t hashi = hs(kot(data)) % _tables.size(); Node* newnode = new Node(data); //头插 newnode->_next = _tables[hashi]; _tables[hashi] = newnode; ++_n; return { {newnode, this}, true }; }
这个就是哈希表改装一下成pair类型返回值,一个迭代器一个bool类型的返回值,首先先找是不是存在,存在直接返回迭代器it,和一个false,不存在加入哈希表内,返回直接返回迭代器内是newnode,this指针,bool返回true。
1.2.7 Find,erase
Find实现直接把原先哈希表实现的Find返回值类型改成迭代器就可以了,erase变都不用变,代码直接参考上一节实现哈希表的就行。
1.3 unordered_map的封装
template<class K,class V,class Hash = HashFunc<K>> 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, Hash>::Iterator iterator; typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::ConstIterator 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 = insert({ key,V() }); return ret.first->second; } private: hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht; };
首先再这里写一个仿函数为了给前面改造的哈希表提供数据,然后begin,end的普通,const版本迭代器封装,也没啥好说的,insert就只是对哈希表的insert的函数的封装再实现,要说一下的就是operator[]了,要访问i位置的value,直接定一个pair<iterator,bool>类型的ret,然后用insert把key传过去,value不用管,默认的都可以,这样它返回的值存到ret,其实就是两层ret,那么要返回的值直接是取第一层ret的first,然后第二层pair里的second就是key对应的value了。
1.4 unordered_set的封装
template<class K,class Hash =HashFunc<K>> class unordered_set { struct SetKeyOfT { const K& operator()(const K& key) { return key; } }; public: typedef typename hash_bucket::HashTable<K,const K, SetKeyOfT, Hash>::Iterator iterator; typedef typename hash_bucket::HashTable<K,const K, SetKeyOfT, Hash>::ConstIterator 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); } private: hash_bucket::HashTable<K,const K, SetKeyOfT, Hash> _ht; };
这里就更没什么好说的传参就是传给哈希表两个同样的key,然后和上面的unordered_map一样,begin和end的普通和const版本迭代器,insert一样的对哈希表的insert的封装再实现。
结语
unordered_map/set的实现就是再哈希表的基础长改装一下,然后再用这两个进行封装,这样就是先对unordered_map/set的模拟实现,当然你也可以写完以后自己测试一下是不是与库里面的相似。
好了,本节就说到这里,如果我有什么写的不好或者不对的地方,欢迎再评论里面说出来,感谢你的阅读。