【C++进阶】十、用哈希表对unordered_set和unordered_map进行封装
创始人
2025-06-01 02:42:48
0

目录

一、改造哈希表

1.1 节点定义

 1.2 哈希表迭代器相关

1.3 哈希表接口相关

二、unordered_set模拟实现代码

三、unordered_map模拟实现代码


一、改造哈希表

使用的代码是之前篇章哈希表的代码,改造后哈希表代码如下:

#pragma once
#include 
#include //开散列(哈希桶)template
struct HashNode
{T _data;HashNode* _next;HashNode(const T& data):_data(data), _next(nullptr){}
};//仿函数
template
struct HashFunc
{size_t operator()(const K& key){return (size_t)key;}
};//仿函数特化
template<>
struct HashFunc
{size_t operator()(const string& key){size_t hash = 0;for (auto ch : key){hash *= 131;//BKDRHash算法hash += ch;}return hash;}
};//声明HashTable,不声明__HTIerator的_ht变量找不到标识符
template
class HashTable;template
struct __HTIerator
{typedef HashNode Node;typedef __HTIerator Self;typedef HashTable HT;//成员变量Node* _node;HT* _ht;//构造函数__HTIerator(Node* node, HT* ht):_node(node),_ht(ht){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& s)const{return _node != s._node;}Self& operator++(){if (_node->_next)//当前桶没有走完,迭代遍历当前桶{_node = _node->_next;}else//当前桶走完了,要找下一个桶的第一个{KeyOfT 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 HashTable
{typedef HashNode Node;//要给__HTIerator类设置成友元,否则__HTIerator类无法访问HashTable的私有成员,ps;_ht->_tables.size()template friend  struct __HTIerator;
public:typedef __HTIerator iterator;//构造HashTable():_n(0){_tables.resize(10);//默认开10个空间}//析构~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;}}iterator begin(){for (size_t i = 0; i < _tables.size(); ++i){if (_tables[i]){return iterator(_tables[i], this);}}return iterator(nullptr, this);}iterator end(){return iterator(nullptr, this);}//插入pair Insert(const T& data){KeyOfT kot;iterator it = Find(kot(data));if (it != end())//查询插入的值是否已经存在{return make_pair(it, false);//存在插入失败}//大于标定负载因子,就需要扩容//这里负载因子标定为 1if (_tables.size() == _n){//创建一个新的哈希表,新哈希表的大小设置为原哈希表的2倍vector newTables;newTables.resize(_tables.size() * 2);//将原哈希表当中的结点插入到新哈希表for (size_t i = 0; i < _tables.size(); ++i){Node* cur = _tables[i];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[i] = nullptr;}//交换这两个哈希表_tables.swap(newTables);}//不需要扩容,进行插入size_t hashi = Hash()(kot(data)) % _tables.size();//头插Node* newNode = new Node(data);newNode->_next = _tables[hashi];_tables[hashi] = newNode;//有效数据+1++_n;return make_pair(iterator(newNode, this), true);}//查找iterator Find(const K& key){size_t hashi = Hash()(key) % _tables.size();//计算key的哈希地址Node* cur = _tables[hashi];//遍历这个桶进行查找while (cur){if (KeyOfT()(cur->_data) == key)//查找成功{return iterator(cur, this);}cur = cur->_next;}//该桶遍历完,查找失败return end();}//删除bool Erase(const K& key){size_t hashi = Hash()(key) % _tables.size();//计算key的哈希地址Node* prev = nullptr;//用于记录 cur的前一个节点Node* cur = _tables[hashi];while (cur){if (KeyOfT()(cur->_data) == key)//找到需要删除的节点{if (cur == _tables[hashi])//头删{_tables[hashi] = cur->_next;}else//中间删除{prev->_next = cur->_next;}delete cur;--_n;return true;}else//迭代遍历{prev = cur;cur = cur->_next;}}//删除失败return false;}
private:vector _tables;//指针数组, 哈希表size_t _n;//用于记录表中有效数据的个数
};

哈希表模板参数的控制:unordered_set是K模型的容器,而unordered_map是KV模型的容器

HashTable类的模板参数介绍:

template

模板参数K(key)用于查找和删除数据,模板参数T用于存储数据,如果上层是unordered_set,T则是key,如果上层是unordered_map,T则是键值对pair

  • 上层容器是unordered_set时,传入的T是键值,哈希结点中存储的就是Key
  • 上层容器是unordered_map时,传入的T是键值对,哈希结点中存储的就是键值对

模板参数Hash是一个哈希函数,在哈希表已经有过详细解释

模板参数KeyOfT 是一个仿函数,用于获取数据,由上层的 unordered_set 和 unordered_map 独立实现

1.1 节点定义

数据类型是泛型,由上层传入才确定是Key 或键值对pair 

template
struct HashNode
{T _data;HashNode* _next;HashNode(const T& data):_data(data), _next(nullptr){}
};

 1.2 哈希表迭代器相关

迭代器模板参数如下

template

哈希表的迭代器与其他容器的迭代器有所不同,哈希表的迭代器增加了一个 HashTable 的指针,不增加这个变量是无法完成哈希表的迭代器的,这也意味着 const 版本的迭代器需要重新写在另一个类,不能复用普通迭代器的代码完成const 迭代器

 构造迭代器时,不仅需要对应哈希结点的指针,还需要该哈希结点所在哈希表的地址

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

前置++

  • 若当前结点不是当前哈希桶中的最后一个结点,则++后走到当前哈希桶的下一个结点
  • 若当前结点是当前哈希桶的最后一个结点,则++后走到下一个非空哈希桶的第一个结点
Self& operator++()
{if (_node->_next)//当前桶没有走完,迭代遍历当前桶{_node = _node->_next;}else//当前桶走完了,要找下一个桶的第一个{KeyOfT 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;
}
};

其他都与之前一致,就不解释了

1.3 哈希表接口相关

详细介绍在哈希表篇章,这里不解释了,这里只是对它的进口进行了封装

二、unordered_set模拟实现代码

都是调用哈希表接口,不解释了

#include "HashTable.h"namespace fy
{template>class unordered_set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public://没有实例化,没办法到HashTable里面找iterator,所以typename就是告诉编译器这里是一个类型,实例化以后再去取typedef typename HashTable::iterator iterator;//typedef typename HashTable::const_iterator const_iterator;iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}/*const_iterator begin()const{return }*/bool erase(const K& key){return _ht.Erase(key);}pair insert(const K& key){return _ht.Insert(key);}private:HashTable _ht;};void Test_unorderSet(){unordered_set us;us.insert(4);us.insert(14);us.insert(8);us.insert(2);us.insert(18);us.insert(2);us.insert(234);us.insert(24);us.insert(11);us.insert(666);unordered_set::iterator it = us.begin();while (it != us.end()){cout << *it << " ";++it;}cout << endl;us.insert(5);us.insert(16);us.erase(4);us.erase(4);us.erase(2);us.erase(11);us.erase(666);us.erase(234);}
}

三、unordered_map模拟实现代码

都是调用哈希表接口,不解释了

#include "HashTable.h"namespace fy
{template>class unordered_map{struct MapKeyOfT{const K& operator()(const pair& kv){return kv.first;}};public:typedef typename HashTable, Hash, MapKeyOfT>::iterator iterator;iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}pair insert(const pair& data){return _ht.Insert(data);}iterator find(const K& key){return _ht.Find(key);}bool erase(const K& key){return _ht.Erase(key);}V& operator[](const K& key){pair ret = _ht.Insert(make_pair(key, V()));//无法直接返回,要使用变量进行接收再返回return ret.first->second;}private:HashTable, Hash, MapKeyOfT> _ht;};void Test_unorderedMap(){string arr[] = { "苹果", "西瓜", "香蕉", "草莓", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };unordered_map countMap;for (auto& e : arr){countMap[e]++;}for (const auto& kv : countMap){cout << kv.first << ":" << kv.second << endl;}}
}

文章内容都是炒旧饭,没啥介绍的,文章就到这

----------------我是分割线---------------

文章到这里就结束了,下一篇即将更新

相关内容

热门资讯

AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWR报告解读 WORKLOAD REPOSITORY PDB report (PDB snapshots) AW...
AWS管理控制台菜单和权限 要在AWS管理控制台中创建菜单和权限,您可以使用AWS Identity and Access Ma...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
群晖外网访问终极解决方法:IP... 写在前面的话 受够了群晖的quickconnet的小水管了,急需一个新的解决方法&#x...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
Azure构建流程(Power... 这可能是由于配置错误导致的问题。请检查构建流程任务中的“发布构建制品”步骤,确保正确配置了“Arti...