C++【哈希表封装unordered_map/set】—含有源代码

news/2024/10/30 15:20:22/

文章目录

    • (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();}

http://www.ppmy.cn/news/292833.html

相关文章

联想 计算机无线网络设置方法,联想笔记本无线网络开关,详细教您联想笔记本无线网络开关...

联想笔记本是联想集团生产的可携带的笔记本&#xff0c;可帮助我们娱乐、办公。笔记本电脑在生活办公中使用很方便&#xff0c;有时候想要连接无线网却看不到图标&#xff0c;一般来说笔记本电脑都配有无线网络快捷开关&#xff0c;那么怎么打开笔记本无线网络&#xff1f;下面…

关于联想笔记本无线网老是掉线的解决方法

因为我遇到了多次联想笔记本wifi老是掉线的情况&#xff0c; 所以我搜集了网上的比较有效的方法 以及自己的做法分享给同为用联想笔记本的兄弟萌&#xff1a; 右键【此电脑】点击【管理】打开 在窗口中点击【设备管理器】选项&#xff0c;点击打开后找到【网络适配器】里的无…

联想笔记本上Ubuntu无线网卡问题

可能有两个问题&#xff1a; 1、无无线网卡驱动 2、无线网卡驱动不能自动加载 问题1&#xff1a;无线网卡驱动 百度出网卡驱动iwlwifi-9000&#xff0c;如iwlwifi-9000-pu-b0-jf-b0-34.618819.0.tar.gz&#xff0c;解压后将文件“.ucode”复制到目录/lib/firmware/ 问题2&am…

apktool for mac

安装步骤 1、Apktool下载 安装apktool Apktool下载 macOS: Download Mac wrapper script (Right click, Save Link As apktool)Download apktool-2 (find newest here)Rename downloaded jar to apktool.jarMove both files (apktool.jar & apktool) to /usr/local/bin …

android联想搜索不到wifi,联想笔记本搜不到无线网解决办法

可能还有些网友对于联想笔记本搜不到无线网的情况不太了解。下面是小编收集整理的&#xff0c;希望对大家有帮助~~ 联想笔记本搜不到无线网的解决方法一&#xff1a; 笔记本搜不到无线信号&#xff0c;可通过如下方式修复&#xff1a; 1***右击“我的电脑”&#xff0c;再选择“…

联想 Yoga C740::关于Ubuntu16.04下无法识别Intel WIFI6 AX201无线网卡的解决方案

关于Ubuntu16.04下无法识别Intel WIFI6 无线网卡的解决方案 环境&#xff1a; 联想 Yoga C740 i5-10210U 16GB 无线网卡 Intel WIFI6 AX201 &#xff08;在自带的Window10 系统的设备管理器下可以看到&#xff09;Ubuntu16.04 LTS 一、安装干净的Ubtuntu16.04 不展开 二、安…

联想i5无线网无法连接服务器,联想笔记本不能连接无线网的解决方法

联想笔记本不能连接无线网的解决方法 急需使用无线网络.可以暂时降低加密协议(如WEP)使用.最有效de解决办法是换一块全新de内置/外置无线网卡.最好选择支持802.11n标准。下面是jy135小编收集整理的联想笔记本不能连接无线网的解决方法&#xff0c;欢迎阅读。 联想笔记本不能连接…

联想服务器电脑找不到wifi网络,联想笔记本无线网络找不到怎么办

笔记本电脑突然之间无线网络连接不见了&#xff0c;&#xff0c;这到底是怎么回事呢&#xff0c;怎么解决!下面学习啦小编给大家讲解一下关于联想笔记本无线网络找不到的解决方法。 联想笔记本无线网络找不到的解决方法 检查自己的笔记本是否打开了wifi 注意&#xff1a;电脑都…