【C++】unordered_map与unorder_set的封装(哈希桶)

文章目录

  • 前言
  • 一、模板参数的改造
  • 二、模板的特例化操作
  • 三、仿函数的妙用
  • 四、unordered迭代器基本操作
    • 1.const迭代器注意:
    • 2.HashTable与HTIterator的冲突
  • 五、迭代器的构造问题
  • 六、完整代码
    • 1.hash_bucket.h
    • 2.unordered_set.h
    • 3.unordered_map.h

前言

在这里插入图片描述
我们开辟一个指针数组,指针数组中存放我们结点的类型,我们算出元素的下标hashi后,头插在数组的对应位置,数组的位置可以链接一串链表,所以也避免了哈希冲突

一、模板参数的改造

我们这里把pair<K,V>看成一个整体,我们设计模板的时候就不需要考虑是不是键值对类型,需不需要多传一个模板参数的问题,达到了普适性。

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

在map中,T传pair<K,V>类型
在set中,T传K类型

二、模板的特例化操作

我们要想插入一个结点,肯定要知道这个结点插入的位置,所以我们要自己写一个哈希函数的模板来进行下标的计算,但我们int类型之间计算下标很容易,那我们字符串该怎么办?
这个时候就需要模板的特例化了,根据传入的参数不同,具体类型具体分析

template<class K>
struct DefaultHashFunc {size_t operator()(const K& key) {return (size_t)key;}
};template<>
struct DefaultHashFunc<string> {//对字符串特殊处理size_t operator()(const string& str) {size_t hash = 0;for (auto e : str) {hash *= 131;hash += e;}//字符串的总和再与131相乘,//减少字符串和雷同的情况return hash;}
};

之后再对算出的hash进行取模的操作

三、仿函数的妙用

我们value_type类型用模板参数T代替之后,这个时候就会衍生一个问题,我T可能为键值对类型,我键值对之间怎么比较呢?
例如:T t1与T t2两个变量,我们肯定不能直接比较,肯定要依据他们的键值大小进行比较,所以我们需要自己写一个用于比较的函数,这个时候仿函数刚好能发挥这个用处,可以作为模板参数传入自己写的比较函数

取出他们的键,让他们进行比较,这里set也这样写是为了配合map,因为两者都用的一个哈希桶模板

struct SetKeyOfT {const K& operator()(const K&key) {return key;}};struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};

四、unordered迭代器基本操作

1.const迭代器注意:

在这里插入图片描述
这里如果使用const迭代器会报错,因为发生了权限的放大

修改方法:在参数的位置以及定义的时候给HashTable加上const
这样当我们传const类型的时候发生权限的平移,传普通类型的时候发生权限缩小const HashTable<K, T, KeyOfT, HashFunc>* _pht;Node* _node;HTIterator(Node*node, const HashTable<K, T, KeyOfT, HashFunc>* pht):_node(node),_pht(pht){}

2.HashTable与HTIterator的冲突

HashTable中用到了HTIterator,因为要创建出迭代器,而我们HTIterator内部使用到了HashTable中的私有。这就造成了先有鸡还是先有蛋的问题。

解决方法:
在HTIterator的类前面进行前置声明。告诉编译器这个HashTable是存在的
在这里插入图片描述
在HashTable中声明友元在这里插入图片描述

五、迭代器的构造问题

在这里插入图片描述
在unordered_set中我们返回的pair里面的iterator是const类型,但我们哈希桶里面写的Insert中的pair返回的是普通迭代器,因为类模板实例化不同的模板参数就是不同的类型,所以这里const_iterator与iterator我们可以看成是两个不相同的类型,如果我们直接传哈希桶里面的Insert返回值会发生报错,因为类型不匹配。
这个时候我们需要一个函数来将iterator类型转变为const_iterator类型,我们可以从迭代器的拷贝构造下手。

typedef HTIterator<  K,  T,   Ptr,   Ref,   KeyOfT,  HashFunc>  Self;
typedef HTIterator<  K, T, T*, T&, KeyOfT, HashFunc> Iterator;
typedef HashNode<T> Node;
const HashTable<K, T, KeyOfT, HashFunc>* _pht;
Node* _node;HTIterator(const Iterator& it):_node(it._node), _pht(it._pht){}

如果调用这个函数的是普通迭代器iterator,这里就是纯拷贝构造
如果调用这个函数的是const_iterator,那么这个函数就是构造,我们可以传入普通迭代器iterator来构造出const_iterator

六、完整代码

1.hash_bucket.h

#pragma once
#include<vector>
#include<string>namespace hash_bucket {template<class T>
struct HashNode {//结点的定义T _data;HashNode<T>* _next;HashNode(const T& data):_data(data),_next(nullptr){}
};template<class K>
struct DefaultHashFunc {//哈希函数进行下标的求取size_t operator()(const K& key) {return (size_t)key;}
};template<>//模板特例化
struct DefaultHashFunc<string> {//对字符串特殊处理size_t operator()(const string& str) {size_t hashi = 0;for (auto e : str) {hashi *= 131;hashi += e;}return hashi;}
};template<class K, class T, class KeyOfT, class HashFunc >
class HashTable;
//前置声明
template<class K, class T, class Ptr, class Ref, class KeyOfT, class HashFunc = DefaultHashFunc<K>>
struct HTIterator {typedef HTIterator<  K, T, Ptr, Ref, KeyOfT, HashFunc>  Self;typedef HTIterator<  K, T, T*, T&, KeyOfT, HashFunc> Iterator;typedef HashNode<T> Node;const HashTable<K, T, KeyOfT, HashFunc>* _pht;//引入哈希桶,因为我们进行++操作的时候需要哈希桶数组来确定位置//这里哈希桶要为const类型Node* _node;HTIterator(Node* node, const HashTable<K, T, KeyOfT, HashFunc>* pht):_node(node),_pht(pht){}HTIterator(const Iterator& it):_node(it._node), _pht(it._pht){}Self& operator++() {if (_node->_next) {//当前链表后面还有_node = _node->_next;}else {//当前链表以及走完KeyOfT kot;HashFunc hf;size_t hashi = hf(kot(_node->_data)) % _pht->_table.size();//kot先取出_node->_data中的K值然后hf算出哈希下标++hashi;//从下一个位置开始while (hashi < _pht->_table.size()) {if (_pht->_table[hashi]) {//找不为空的位置_node = _pht->_table[hashi];return*this;}else {hashi++;}}_node = nullptr;}return *this;}Ref operator*() {return _node->_data;}Ptr operator->() {return &_node->_data;}bool operator!=(const Self& s){return _node != s._node;}bool operator==(Self& s) {return _node == s._node;}};template<class K,class T,class KeyOfT,class HashFunc=DefaultHashFunc<K>>
//KeyOfT的作用是取出T里面的K值,因为T有可能为pair类型
class HashTable {typedef HashNode<T> Node;
public:template<class K, class T, class Ptr, class Ref, class KeyOfT, class HashFunc >friend struct HTIterator;//友元的引入typedef HTIterator<K, T, T*, T&, KeyOfT, HashFunc> iterator;typedef HTIterator<K, T, const T*, const T&, KeyOfT, HashFunc> const_iterator;iterator begin() {//找到数组的第一个不为空的位置for (size_t i = 0; i < _table.size(); i++) {if (_table[i]) {return iterator(_table[i], this);}}return iterator(nullptr, this);}iterator end() {return iterator(nullptr, this);}const_iterator begin()const {for (size_t i = 0; i < _table.size(); i++) {if (_table[i]) {return const_iterator(_table[i], this);}}return const_iterator(nullptr, this);}const_iterator end()const {return const_iterator(nullptr, this);}HashTable(){//构造函数_table.resize(10,nullptr);}~HashTable() {//析构函数for (size_t i = 0; i < _table.size(); i++) {Node* cur = _table[i];while (cur) {Node* first = cur->_next;delete cur;cur = first;}_table[i] = nullptr;}}iterator Find(const K& key){HashFunc hf;KeyOfT kot;size_t hashi = hf(key) % _table.size();Node* cur = _table[hashi];while (cur){if (kot(cur->_data) == key){return iterator(cur, this);}cur = cur->_next;}return end();}pair<iterator, bool> Insert(const T& data){KeyOfT kot;iterator it = Find(kot(data));if (it != end()){return make_pair(it, false);}HashFunc hf;// 负载因子到1就扩容if (_n == _table.size()){//size_t newSize = _table.size() * 2;size_t newSize = _table.size()*2;vector<Node*> newTable;newTable.resize(newSize, nullptr);// 遍历旧表,顺手牵羊,把节点牵下来挂到新表for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];while (cur){Node* next = cur->_next;// 头插到新表size_t hashi = hf(kot(cur->_data)) % newSize;cur->_next = newTable[hashi];newTable[hashi] = cur;cur = next;}_table[i] = nullptr;}_table.swap(newTable);}size_t hashi = hf(kot(data)) % _table.size();// 头插Node* newnode = new Node(data);newnode->_next = _table[hashi];_table[hashi] = newnode;++_n;return make_pair(iterator(newnode, this), true);}bool Erase(const K& key) {HashFunc hf;size_t hashi = hf(key) % _table.size();Node* prev = nullptr;Node* cur = _table[hashi];while (cur) {if (kot(cur->_data) == key) {if (prev == nullptr) {_table[hashi] = nullptr;}else {Node* next = cur->_next;prev->_next = next;}delete cur;return true;}prev = cur;cur = cur->_next;}--_n;return false;}private:vector<Node*> _table;//定义指针数组size_t _n;
};}

2.unordered_set.h

#pragma once
#include"hash_bucket.h"namespace bit {template<class K>class unordered_set {struct SetKeyOfT {const K& operator()(const K& key) {return key;}};public:typedef typename hash_bucket::HashTable<K, K, SetKeyOfT>::const_iterator iterator;//重点typedef typename hash_bucket::HashTable<K, K, SetKeyOfT>::const_iterator const_iterator;iterator begin()const {return _ht.begin();}iterator end()const {return _ht.end();}pair<iterator,bool>insert(const K& key) {pair< hash_bucket::HashTable<K, K, SetKeyOfT>::iterator,bool>ret= _ht.Insert(key);//先拿到为普通迭代器的iteratorreturn pair<iterator, bool>(ret.first, ret.second);//用普通迭代器构造const迭代器}private:hash_bucket::HashTable<K, K, SetKeyOfT> _ht;};}

3.unordered_map.h

#pragma once
#include"hash_bucket.h"namespace bit {template<class K,class V>class unordered_map {struct MapKeyOfT {const K& operator()(pair<K,V>kv) {return kv.first;}};public:typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT>::iterator iterator;typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;pair<iterator, bool> insert(const pair<K, V>& kv){return _ht.Insert(kv);}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 = _ht.Insert(make_pair(key, V()));return ret.first->second;}private:hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT> _ht;};}

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

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

相关文章

Docker网络问题:容器无法访问外部网络

Docker网络问题&#xff1a;容器无法访问外部网络 &#x1f61f; Docker网络问题&#xff1a;容器无法访问外部网络 &#x1f61f;摘要 &#x1f914;引言 &#x1f310;正文 &#x1f913;为什么容器无法访问外部网络&#xff1f; &#x1f615;1. 网络配置错误2. 防火墙设置3…

二分类问题的解决利器:逻辑回归算法详解(一)

文章目录 &#x1f34b;引言&#x1f34b;逻辑回归的原理&#x1f34b;逻辑回归的应用场景&#x1f34b;逻辑回归的实现 &#x1f34b;引言 逻辑回归是机器学习领域中一种重要的分类算法&#xff0c;它常用于解决二分类问题。无论是垃圾邮件过滤、疾病诊断还是客户流失预测&…

中级职称评审论文重要吗?是不是必须要论文呢?

现在评中级职称职称对论文有什么要求&#xff1f;没有论文可以参与职称评审吗&#xff1f; 建筑中级职称怎么评&#xff1f;那自然是从多方面来考核人才是否具备了评中级工程师的能力&#xff0c;职称论文就是考核的标准之一。 甘建二告诉你&#xff0c;现在评职称论文是很重…

新增MariaDB数据库管理、支持多版本MySQL数据库共存,1Panel开源面板v1.6.0发布

2023年9月18日&#xff0c;现代化、开源的Linux服务器运维管理面板1Panel正式发布v1.6.0版本。 在这个版本中&#xff0c;1Panel新增MariaDB数据库管理&#xff1b;支持多版本MySQL数据库共存&#xff1b;支持定时备份系统快照和应用商店中已安装应用&#xff1b;支持为防火墙…

优维低代码实践:图片和搜索

优维低代码技术专栏&#xff0c;是一个全新的、技术为主的专栏&#xff0c;由优维技术委员会成员执笔&#xff0c;基于优维7年低代码技术研发及运维成果&#xff0c;主要介绍低代码相关的技术原理及架构逻辑&#xff0c;目的是给广大运维人提供一个技术交流与学习的平台。 优维…

字符串函数----篇章(1)

目录 补上章缺失的两道题 七.笔试题&#xff08;7&#xff09; 八.笔试题&#xff08;8&#xff09; 一.字符串函数 ( 1 )----strlen函数 二.字符串函数 ( 2 )----strcpy函数 2-1模拟实现strcpy 三.字符串函数 ( 3 )----strcmp函数 ​编辑 3-1模拟实现strcmp 四.字符串函…

phpstudy脚本编写 和sql注入编写

1.phpstudy编写 2.sql注入编写

C++之template可变模板参数应用总结(二百二十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

自动化测试:yaml结合ddt实现数据驱动!

在pythonunittestseleniumddt的框架中&#xff0c;数据驱动常见有以下几种方式实现&#xff1a; Csv/txtExcelYAML 本文主要给大家介绍测试数据存储在YAML文件中的使用场景。首先先来简单介绍一下YAML。 1. 什么是YAML 一种标记语言类似YAML&#xff0c;它实质上是一种通用…

vue若依前端项目搭建

1.项目搭建 首先进入到你需要创建的项目目录下面&#xff0c;然后输入命令vue create .创建项目 接下来选择手动搭建&#xff0c;然后把下面图片中的内容选上 再然后继续配置一些参数信息 接下来运行npm run serve项目就启动起来了 2.配置登录界面文件 首先修改src/router…

跟着官方学jnindk

安装及配置 NDK 和 CMake 如需为您的应用编译和调试原生代码&#xff0c;您需要以下组件&#xff1a; 1.Android 原生开发套件 (NDK)&#xff1a;这是一套可让您在 Android 应用中使用 C 和 C 代码的工具。 2.CMake&#xff1a;这是一款外部构建工具&#xff0c;可与…

C++之浅拷贝、深拷贝、拷贝构造函数、拷贝赋值运算符、自定义的深拷贝函数应用总结(二百二十九)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

四、数学建模之图与网络模型

1.定义 2.例题及软件代码求解 一、定义 1.图和网络是相关概念 &#xff08;1&#xff09;图&#xff08;Graph&#xff09;&#xff1a;图是数学和计算机科学中的一个抽象概念&#xff0c;它由一组节点&#xff08;顶点&#xff09;和连接这些节点的边组成。图可以是有向的&…

vector使用和模拟实现

&#x1f493;博主个人主页:不是笨小孩&#x1f440; ⏩专栏分类:数据结构与算法&#x1f440; C&#x1f440; 刷题专栏&#x1f440; C语言&#x1f440; &#x1f69a;代码仓库:笨小孩的代码库&#x1f440; ⏩社区&#xff1a;不是笨小孩&#x1f440; &#x1f339;欢迎大…

wx.getPrivacySetting 小程序隐私保护指引的使用(复制粘贴即用)

创建privacyPopup 组件 privacyPopup.js Component({properties: {},data: {wxPrivacyName: ,showAgreement: false},lifetimes: {attached() {this.init();}},methods: {async init() {if (isLogin()) {const userPrivacy await this.getPrivacy();this.setData({wxPrivacy…

C语言文件的相关操作

C语言中文件的相关操作 文件的打开 使用文件的打开函数需要引入这个头文件&#xff1a;#include <fcntl.h> open函数 int open(char const *pathname, int flags, mode_t mode) 功能&#xff1a;打开已有的文件或者创建新文件参数 pathname&#xff1a;文件路径名&…

Vue 使用vue-cli构建SPA项目(超详细)

目录 一、什么是vue-cli 二&#xff0c;构建SPA项目 三、 运行SPA项目 前言&#xff1a; 在我们搭建SPA项目时候&#xff0c;我们必须去检查我们是否搭建好NodeJS环境 cmd窗口输入以下指令&#xff1a;去检查 node -v npm -v 一、什么是vue-cli Vue CLI&#xff08;Vu…

控制台日志打印console的封装,加入美化、行显示与打印开关,支持node.js环境

控制台日志打印console的封装&#xff0c;加入美化、行显示与打印开关&#xff0c;支持node.js环境 为什么要写这个&#xff1f; 封装这个控制台日志打印工具&#xff0c;主要是在项目中自己做的SDK需要提供给其他开发人员使用&#xff0c;加入了日志美化和打印打开&#xff…

jq命令安装与使用

目录 一、简介二、下载及安装1.Linux 安装2.Windows 安装3.测试安装结果 三、jq用法1.基本语法2.常见用法1&#xff09;格式化 JSON2&#xff09;获取属性3&#xff09;属性不存在情况处理4&#xff09;数组遍历、截取、展开5&#xff09;管道、逗号、加号6&#xff09;数据构造…

Linux 系统目录结构 终端

系统目录结构 Linux 或 Unix 操作系统中&#xff0c;所有文件和目录呈一个以根节点为始的倒置的树状结构。文件系统的最顶层是根目录&#xff0c;用 / 来表示根目录。在根目录之下的既可以是目录&#xff0c;也可以是文件&#xff0c;而每一个目录中又可以包含子目录文件。如此…