C++——哈希(二)unordered_map和unordered_set的封装

前言

在上一篇文章中我们已经对闭散列的哈希表(线性探测法)和开散列的哈希表(哈希桶)进行了简单的模拟实现,由于是简单实现,功能简单、没有迭代器且不支持不同的类型(非泛型编程)。此时我们这篇文章主要是对上次开散列哈希表的完善并用其封装出unordered_map和unordered_set

上次哈希桶的代码

#pragma once
#include <vector>
//版本1
namespace HashBucket
{template <class K,class V>struct HashNode//表的节点(桶口){HashNode<K,V>* _next;pair<K, V> _kv;HashNode(const pair<K,V>& kv):_next(nullptr),_kv(kv){}};template<class K, class V>class HashTable{typedef HashNode<K,V> Node;public:HashTable(){_tables.resize(10, nullptr);_n=0;}Node* Find(const K& key){size_t hashi = key % _tables.size();//找下标Node* cur = _tables[hashi];while (cur)//走链表{if (cur->_kv.first == key)//找到了{return cur;}cur = cur->_next;//往下走}//找不到return nullptr;}bool Insert(const pair<K, V>& kv){//插入失败(找不到)if (Find(kv.first))return false;//扩容if (_n == _tables.size())//负载因子极限{vector< Node*> newtable(_tables.size() * 2, nullptr);//把旧桶的节点挂到新表新桶中for (size_t i=0;i<_tables.size();i++){Node* cur = _tables[i];//遍历走一个个旧桶while (cur){Node* next = cur->_next;//存一下next,待会可以往下走size_t hashi = cur->_kv.first % newtable.size();//节点挂到新桶的下标可能不一样//挂上新桶cur->_next = newtable[hashi];newtable[hashi] = cur;//往下走cur = next;}//把旧桶被移走的节点置空_tables[i] = nullptr;}_tables.swap(newtable);}size_t hashi = kv.first % _tables.size();//找到下标Node* newnode = new Node(kv);//开一个节点的空间//头插(入桶)newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return true;}bool Erase(const K& key){size_t hashi = key % _tables.size();Node* prev = nullptr;//保存遍历节点的上一个Node* cur = _tables[hashi];while (cur)	{if (cur->_kv.first == key)//找到{//删除if (prev)//节点有可能是头节点  prev非空,不是头节点{prev->_next = cur->_next;}else//要删除的节点是头节点{_tables[hashi] = cur->_next;}delete cur;_n--;return true;}prev = cur;cur = cur->_next;}return false;}private:vector<Node*> _tables; // 指针数组size_t _n;};}

1.哈希表的改造

哈希表里面不止能存数字,还可能存字符串之类的,当然我们上面的哈希表是存不了字符串的,如果硬用字符串那么会报错:string类不能取模 

1.1模板参数的改造

那么解决办法是什么呢?可以用仿函数去解决,也就是再加一层映射

若key不是整形先整成整形再与位置建立关系

template<class K>
struct HashFunc
{size_t operator()(const K& key){return (size_t)key;}
};
template<>
struct HashFunc<string>
{size_t operator()(const string& s){size_t hash = 0;for (auto e : s){hash += e;hash *= 131;}return hash;}
};template<class K, class V,class Hash = HashFunc<K>>
class HashTable
{typedef HashNode<K,V> Node;
public://...
private:vector<Node*> _tables; // 指针数组size_t _n;
};

1.2对不同的成员函数进行修改

存不同类型的值,对应不同的仿函数

然后哈希表中的成员函数大部分都要修改(凡是遇到求下标取模的),都要用仿函数去获取

比如:下面的Find

Node* Find(const K& key)
{Hash hs;size_t hashi = hs(key) % _tables.size();//找下标Node* cur = _tables[hashi];while (cur)//走链表{if (cur->_kv.first == key)//找到了{return cur;}cur = cur->_next;//往下走}//找不到return nullptr;
}

当然insert和erase也要

bool Insert(const pair<K, V>& kv)
{Hash hs;//插入失败(找不到)if (Find(kv.first))return false;//扩容if (_n == _tables.size()){vector< Node*> newtable(_tables.size() * 2, nullptr);//把旧桶的节点挂到新表新桶中for (size_t i=0;i<_tables.size();i++){Node* cur = _tables[i];while (cur){Node* next = cur->_next;//找到下标,取模要调仿函数size_t hashi = hs(cur->_kv.first) % newtable.size();//挂上新桶cur->_next = newtable[hashi];newtable[hashi] = cur;//往下走cur = next;}//把旧桶被移走的节点置空_tables[i] = nullptr;}_tables.swap(newtable);}size_t hashi = hs(kv.first) % _tables.size();//找到下标,取模要调仿函数Node* newnode = new Node(kv);//头插(入桶)newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return true;}bool Erase(const K& key)
{Hash hs;size_t hashi = hs(key) % _tables.size();Node* prev = nullptr;//保存遍历节点的上一个Node* cur = _tables[hashi];while (cur)	{if (cur->_kv.first == key)//找到{//删除if (prev)//节点有可能是头节点  prev非空,不是头节点{prev->_next = cur->_next;}else//要删除的节点是头节点{_tables[hashi] = cur->_next;}delete cur;_n--;return true;}prev = cur;cur = cur->_next;}return false;
}

2.再次改造哈希表

把哈希表封装出unordered_map和unordered_set,这和之前红黑树封装出map和set类似

	template<class K, class T>class HashTable

对于unordered_map

template<class K, class V>
class myunorderedmap
{
public://...
private:HashTable<K, pair<const K, V>> _ht;
};

对于unordered_set

template<class K>
class myunorderedset
{
public://...
private:HashTable<K, const K> _ht;
};

泛型编程 T可以是K也可以是pair<K,V>,所以节点的模板参数和存储的数据类型都要修改

template <class T>
struct HashNode//表的节点(桶口)
{HashNode<T>* _next;T _data;HashNode(const T& data):_next(nullptr), _data(data){}
};

当然别忘了上面的更改(仿函数),不过仿函数还是由unordered_map和unordered_set来决定比较好;后面用到的data(上面代码中的kv.first)可能是unordered_map的键值对(pair<K,V>)也可能是unordered_set的键(key),底层哈希表并不知道是哪个,所以还要用一个仿函数来解决这一问题

那么对于unordered_map增加MapKeyOfT

template<class K, class V, class Hash = HashFunc<K>>
class myunorderedmap
{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};
public://...
private:HashTable<K, pair<const K, V>,MapKeyOfT,Hash> _ht;
};

对于unordered_set增加SetKeyOfT

template<class K, class Hash = HashFunc<K>>
class myunorderedset
{struct SetKeyOfT{const K& operator()(const K& key){return key;}};
public://...
private:HashTable<K, const K,SetKeyOfT,Hash> _ht;
};

当然哈希表也要有对应的接收

template<class K, class T, class KeyOfT, class Hash>
class HashTable
{typedef HashNode<T> Node;
public://...
private:vector<Node*> _tables; // 指针数组size_t _n;
};

这样一来,如果是unordered_map调用哈希表,就会用MapKeyOfT传给哈希表,如果是unordered_set调用哈希表,就用SetKeyOfT传给哈希表,那么哈希表就可以知道T是键还是键值对

既然这些增加了对应的仿函数和模板参数,和上文的仿函数类似,所有成员函数中需要用到key的都要用KeyOfT创建的对象去替换

KeyOfT kot;
kot(data);

find函数修改

Node* Find(const K& key)
{Hash hs;KeyOfT kot;size_t hashi = hs(key) % _tables.size();//找下标Node* cur = _tables[hashi];while (cur)//走链表{if (kot(cur->_data) == key)//找到了{return cur;}cur = cur->_next;//往下走}//找不到return nullptr;
}

 insert修改

bool Insert(const T& data)
{Hash hs;KeyOfT kot;//插入失败(找不到)if(Find(kot(data)))return false;//扩容if (_n == _tables.size())//负载因子极限{vector< Node*> newtable(_tables.size() * 2, nullptr);//把旧桶的节点挂到新表新桶中for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];//遍历走一个个旧桶while (cur){Node* next = cur->_next;//存一下next,待会可以往下走size_t hashi = hs(kot(cur->_data)) % newtable.size();//挂上新桶cur->_next = newtable[hashi];newtable[hashi] = cur;//往下走cur = next;}//把旧桶被移走的节点置空_tables[i] = nullptr;}_tables.swap(newtable);}size_t hashi = hs(kot(data)) % _tables.size();//找到下标Node* newnode = new Node(data);//开一个节点的空间//头插(入桶)newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return true;}

erase修改

bool Erase(const K& key)
{KeyOfT kot;Hash hs;size_t hashi = hs(key) % _tables.size();Node* prev = nullptr;Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == key){// 删除if (prev){prev->_next = cur->_next;}else{_tables[hashi] = cur->_next;}delete cur;--_n;return true;}prev = cur;cur = cur->_next;}return false;
}

3.具体封装及其迭代器实现

3.1哈希表的构造与析构

构造还是之前的构造

至于析构,先遍历表(一个个桶往后走),再遍历表的过程中遍历桶

具体来说表走第一个桶,然后第一个桶往下走(遍历)删除节点,第一个桶删完,表往后走到第二个桶,第二个桶往下走删除节点,表往后走到第三个桶......

HashTable()//构造
{_tables.resize(10, nullptr);_n = 0;
}
~HashTable()//析构
{for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur){Node* next = cur->_next;delete cur;_n--;cur = next;}_tables[i] = nullptr;}
}

3.2迭代器的实现

3.2.1迭代器的框架

template<class K, class T, class KeyOfT, class Hash>
struct HTIterator
{typedef HashNode<T> Node;typedef HashTable<K, T, KeyOfT, Hash> HT;typedef HTIterator<K, T, KeyOfT, Hash> Self;
public:Node* _node;HT* _ht;
}

这里要注意的是如果哈希表的实现是再迭代器的下面的话,迭代器的上面要对哈希表进行前置声明,不然编译器向上搜索不到哈希表的话会报错

加了前置声明的话就不会 

 3.2.2迭代器的构造

HTIterator(Node* node,HT* ht)//构造:_node(node),_ht(ht)
{}

3.2.3重载*操作符和重载->操作符

T& operator*()
{return _node->_data;
}
T* operator->()
{return &_node->_data;
}

3.2.4重载操作符==和!=

bool operator==(const Self& it) const
{return _node == it._node;
}
bool operator!=(const Self& it) const
{return _node != it._node;
}

3.2.5重载操作符++

如果当前桶还没走完,那么就这个桶(链表)继续往下走

如果走完了找下一个非空的桶,找到了下一个非空的桶则走到它的第一个节点,没找到走到空

Self& operator++()
{if (_node->_next)//当前桶还未走完{_node = _node->_next;}else{Hash hs;KeyOfT kot;//当前桶走完了,找下一个桶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;}hashi++;}if (hashi == _ht->_tables.size())//走到后面没有桶了{_node = nullptr;}}return *this;
}

4.源码

4.1unordered_map

#pragma once#include"MyHash.h"namespace HashBucket
{template<class K,class V, class Hash = HashFunc<K>>class myunorderedmap{struct MapKeyOfT{const K& operator()(const pair<K,V>& kv){return kv.first;}};public:typedef typename HashBucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::iterator iterator;iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}bool Insert(const pair<K,V>& kv){return _ht.Insert(kv);}private:HashBucket::HashTable<K,pair<const K,V>,MapKeyOfT, Hash> _ht;};void test_map1(){myunorderedmap<string, string> dict;dict.Insert(make_pair("banana", "香蕉"));dict.Insert(make_pair("strawberry", "草莓"));dict.Insert(make_pair("orange", "橙子"));dict.Insert(make_pair("watermelon", "西瓜"));dict.Insert(make_pair("banana", "香蕉"));dict.Insert(make_pair("strawberry", "草莓"));for (auto& e : dict){cout << e.first << ":" << e.second<<endl;}}
}

4.2unordered_set

#pragma once#include"MyHash.h"namespace HashBucket
{template<class K,class Hash = HashFunc<K>>class myunorderedset{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename HashBucket::HashTable<K, const K, SetKeyOfT, Hash>::iterator iterator;iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}bool insert(const K& key){return _ht.Insert(key);}private:HashBucket::HashTable<K,const K,SetKeyOfT, Hash> _ht;};void test_set1(){myunorderedset<int> us;us.insert(19);us.insert(10);us.insert(11);us.insert(2);us.insert(9);us.insert(7);us.insert(3);us.insert(26);myunorderedset<int>::iterator it = us.begin();while(it!=us.end()){cout << *it << " ";++it;}cout << endl;for (auto& e : us){cout << e << " ";}cout << endl;}
}

4.3Hash

#pragma once
#include<vector>
template<class K>
struct HashFunc
{size_t operator()(const K& key){return (size_t)key;}
};// 特化
template<>
struct HashFunc<string>
{size_t operator()(const string& s){size_t hash = 0;for (auto e : s){hash += e;hash *= 131;}return hash;}
};
namespace HashBucket
{template <class T>struct HashNode//表的节点(桶口){HashNode<T>* _next;T _data;HashNode(const T& data):_next(nullptr), _data(data){}};//前置声明template<class K, class T, class KeyOfT, class Hash>class HashTable;template<class K, class T, class KeyOfT, class Hash>struct HTIterator{typedef HashNode<T> Node;typedef HashTable<K, T, KeyOfT, Hash> HT;typedef HTIterator<K, T, KeyOfT, Hash> Self;public:Node* _node;HT* _ht;HTIterator(Node* node,HT* ht)//构造:_node(node),_ht(ht){}T& operator*(){return _node->_data;}T* operator->(){return &_node->_data;}Self& operator++(){if (_node->_next)//当前桶还未走完{_node = _node->_next;}else{Hash hs;KeyOfT kot;//当前桶走完了,找下一个桶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;}hashi++;}if (hashi == _ht->_tables.size())//走到后面没有桶了{_node = nullptr;}}return *this;}bool operator==(const Self& it) const{return _node == it._node;}bool operator!=(const Self& it) const{return _node != it._node;}};template<class K, class T, class KeyOfT, class Hash>class HashTable{template<class K, class T, class KeyOfT, class Hash>friend struct HTIterator;typedef HashNode<T> Node;public:typedef HTIterator<K, T, KeyOfT, Hash> iterator;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);}HashTable()//构造{_tables.resize(10, nullptr);_n = 0;}~HashTable()//析构{for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur){Node* next = cur->_next;delete cur;_n--;cur = next;}_tables[i] = nullptr;}}bool Insert(const T& data){Hash hs;KeyOfT kot;//插入失败(找不到)if(Find(kot(data)))return false;//扩容if (_n == _tables.size())//负载因子极限{vector< Node*> newtable(_tables.size() * 2, nullptr);//把旧桶的节点挂到新表新桶中for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];//遍历走一个个旧桶while (cur){Node* next = cur->_next;//存一下next,待会可以往下走size_t hashi = hs(kot(cur->_data)) % newtable.size();//节点挂到新桶的下标可能不一样//挂上新桶cur->_next = newtable[hashi];newtable[hashi] = cur;//往下走cur = next;}//把旧桶被移走的节点置空_tables[i] = nullptr;}_tables.swap(newtable);}size_t hashi = hs(kot(data)) % _tables.size();//找到下标Node* newnode = new Node(data);//开一个节点的空间//头插(入桶)newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return true;}Node* Find(const K& key){Hash hs;KeyOfT kot;size_t hashi = hs(key) % _tables.size();//找下标Node* cur = _tables[hashi];while (cur)//走链表{if (kot(cur->_data) == key)//找到了{return cur;}cur = cur->_next;//往下走}//找不到return nullptr;}bool Erase(const K& key){KeyOfT kot;Hash hs;size_t hashi = hs(key) % _tables.size();Node* prev = nullptr;Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == key){// 删除if (prev){prev->_next = cur->_next;}else{_tables[hashi] = cur->_next;}delete cur;--_n;return true;}prev = cur;cur = cur->_next;}return false;}private:vector<Node*> _tables; // 指针数组size_t _n;};
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/588920.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

xray扫描器安装

项目地址 xray community 访问进入xray目录选择适合自己的版本 下载解压 winR输入powershell cd进入xray目录 输入下述命令生成证书 .\xray_windows_amd64.exe genca 在浏览器中设置中找到隐私管理证书 点击管理证书&#xff0c;导入 找到之前xray的目录导入证书 点击浏览…

【Linux】Vim编辑器

专栏文章索引&#xff1a;Linux 目录 在Vim编辑器中&#xff0c;一个Tab键相当于几个空格&#xff1f; 在Vim编辑器中&#xff0c;一个Tab键相当于几个空格&#xff1f; 在Vim编辑器中&#xff0c;默认情况下&#xff0c;一个Tab键相当于8个空格。 这是Vim的默认设置&#x…

16.springboot项目下使用事务(springboot-016-transaction)

事务是一个完整的功能&#xff0c;也叫作是一个完整的业务 事务只跟什么SQL语句有关&#xff1f;事务只跟DML语句有关系&#xff1a;增删改 DML,DQL,DDL,TCL,DCL 首先添加两个依赖以及MyBatis代码自动生成插件 <!--MySql驱动--><dependency><groupId>mysql…

Verilog语法回顾--用户定义原语

目录 用户定义原语 UDP定义 UDP状态表 状态表符号 组合UDP 电平敏感UDP 沿敏感时序UDP 参考《Verilog 编程艺术》魏家明著 用户定义原语 用户定义原语&#xff08;User-defined primitive&#xff0c;UDP&#xff09;是一种模拟硬件技术&#xff0c;可以通过设计新的原…

一文解析智慧城市,人工智能技术将成“智”理主要手段

长期以来&#xff0c;有关智慧城市的讨论主要围绕在技术进步方面&#xff0c;如自动化、人工智能、数据的公开以及将更多的传感器嵌入城市以使其更加智能化。实际上&#xff0c;智慧城市是一个关于未来的设想&#xff0c;其重要原因在于城市中存在各种基础设施、政治、地理、财…

C语言运算符和表达式——赋值中的自动类型转换(精度损失问题)

目录 自动类型转换 数值精度损失 自动类型转换 在不同类型数据间赋值时&#xff0c;会发生自动类型转换 *取值范围大的类型 → 取值范围小的类型&#xff0c;通常是不安全的 *数值溢出&#xff08;Overflow&#xff09; *反之&#xff0c;一定都是安全的吗&#xff1f;…

Flutter应用图标、截图与描述优化:提升上架成功率的关键技巧

引言 Flutter是一款由Google推出的跨平台移动应用开发框架&#xff0c;其强大的性能和流畅的用户体验使其备受开发者青睐。然而&#xff0c;开发一款应用只是第一步&#xff0c;将其成功上架到苹果商店才是实现商业目标的关键一步。本文将详细介绍如何使用Flutter将应用程序上…

k8s的pod访问service的方式

背景 在k8s中容器访问某个service服务时有两种方式&#xff0c;一种是把每个要访问的service的ip注入到客户端pod的环境变量中&#xff0c;另一种是客户端pod先通过DNS服务器查找对应service的ip地址&#xff0c;然后在通过这个service ip地址访问对应的service服务 pod客户端…

每日一题(leetcode2952):添加硬币最小数量 初识贪心算法

这道题如果整体去思考&#xff0c;情况会比较复杂。因此我们考虑使用贪心算法。 1 我们可以假定一个X&#xff0c;认为[1,X-1]区间的金额都可以取到&#xff0c;不断去扩张X直到大于target。&#xff08;这里为什么要用[1,X-1]而不是[1,X],总的来说是方便&#xff0c;潜在思想…

门户系统—分类信息

分类信息&#xff1a;轻松构建本地生活信息网 分类信息是指按照一定分类标准发布的信息&#xff0c;例如房产、招聘、二手物品等。分类信息网站可以帮助用户发布和查找本地生活信息&#xff0c;满足用户的蓝牙需求。 分类信息网站的功能&#xff1a; 信息发布&#xff1a;用…

【C++面向对象】C++图书管理系统 (源码)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

Spring MVC 的执行流程

Spring MVC 的执行流程 1、用户输入 URL 或 点击链接&#xff0c;浏览器将发送 HTTP 请求到服务器 2、请求首先到达 Spring MVC 的前端控制器 DispatcherServlet 3、前端控制器通过处理器映射器 HandlerMapping 根据请求 URL 找到对应的处理器 handler 4、前端控制器使用处理…