哈希封装unordered系列关联式容器

文章目录

    • 补档
    • HashTable迭代器
      • 基本框架
      • 具体实现
    • HashTable模板化
      • 具体实现
    • UnorderedSet封装
      • 具体实现
    • UnorderMap封装

补档

上一次我们在使用哈希函数时说,利用仿函数可以解决不知道哈希表内存的数据类型时对哈希函数也可以进行计算,但是当时只给了一个框架,一种是可以直接强制类型转换为整型的,另一种是字符串类型

对于字符串的哈希函数有很多研究,一种比较方便的做法是使用字符串的第一个字符的ASCII码值进行简单的数学运算,或者把所有的ASCII码值相加再运算,因为如果直接作为哈希地址会导致很多重复的冲突

template<>
struct HashFunc<string> {
	size_t operator()(const string& key) {
		size_t hash = 0;
		for (auto e : key) {
			hash = hash * 31 + e;
		}
		return hash;
	}
};

HashTable迭代器

开放定址法比较简单,这里我们主要实现连定址法

和之前一样,我们封装需要增加迭代器、和他的基本操作,还有KeyOfT,然后再遇到什么问题我们详细说

首先第一个问题是,我们要把数据类型从原来的key、value模式转化为模板,变成这种形式

	template<class T>
	struct HashNode {
		HashNode* _next;
		T _data;

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

然后我们就要实现哈希表的迭代器了

基本框架

	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 __HTIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;

		Node* _node;
		const HashTable<K, T, KeyOfT, Hash>* _pht;
		size_t _hashi;

		__HTIerator(Node* node, HashTable<K, T, KeyOfT, Hash>* pht, size_t hashi)
			:_node(node)
			, _pht(pht)
			, _hashi(hashi) {}

		__HTIerator(Node* node, const HashTable<K, T, KeyOfT, Hash>* pht, size_t hashi)
			:_node(node)
			, _pht(pht)
			, _hashi(hashi) {}

		Self& operator++() {}
		Ref operator*() {}
		Ptr operator->() {}

		bool operator!=(const Self& s) {}

	};

这里的迭代器因为要访问哈希表,所以需要前置声明,访问哈希表的话,我们给一个pht指向他的节点的指针,然后给一个对应的哈希地址

具体实现

		Self& operator++() {
			if (_node->_next != nullptr) {
				_node = _node->_next;
			}
			else {
				_hashi++;
				while (_hashi < _pht->_tables.size()) {
					if (_pht->_tables[_hashi]!=nullptr);
					{
						_node = _pht->_tables[_hashi];
						break;
					}
					_hashi++}
				
				if (_hashi == _pht->_tables.size()) {
					_node = nullptr;
				}
			}
			return *this;
		}

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

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

这里实现++主要有一个难点,就是当一个哈希桶走完的时候,我们如何找到下一个有效的哈希桶,一种思路是当场直接计算,取到下一个表中的值,计算出对应的哈希地址,第二种是直接现场推演,只要没有走出整个表,当我们找到一个不为空的节点时,这就是++的下一个位置,如果一直走出了表,就说明到了结尾,赋值为空即可

指针,引用,不等于,这里很简单

HashTable模板化

同样的,我们实现了迭代器之后,哈希表就也需要模板化了,由于迭代器内部使用了哈希表中的数据,我们还需要给哈希表中添加迭代器友元

具体实现

	template<class K, class T, class KeyOfT, class Hash>
	class HashTable {
		typedef HashNode<T> Node;

		template<class K, class T, class Ref, class KeyOfT, class Hash>
		friend struct __HTIterator;
	public:
		typedef __HTIterator<K, T, T&, T*, KeyOfT, Hash> iterator;
		typedef __HTIterator<K, T, const T&, const T*, KeyOfT, Hash> const_iterator;

		iterator begin() {
			for (size_t i = 0; i < _tables.size(); i++) {
				if (_tables[i]) {
					return iterator(_tables[i], this, i);
				}
			}

			return end();
		}

		iterator end() {
			return iterator(nullptr, this, -1);
		}

		iterator begin() const {
			for (size_t i = 0; i < _tables.size(); i++) {
				if (_tables[i]) {
					return const_iterator(_tables[i], this, i);
				}
			}

			return end();
		}

		iterator end() const {
			return const_iterator(nullptr, this, -1);
		}


		HashTable() {
			_tables.resize(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;
			}
		}

		 pair<iterator, bool> Insert(const T& data) {
			Hash hf;
			KeyOfT kot;

			iterator it = Find(kot(data));
			if (it != end())
				return make_pair(it, false);

			if (_n == _tables.size()) {
				vector<Node*> newTables;
				newTables.resize(_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 = hf(kot(cur->_data)) % newTables.size();
						cur->_next = newTables[i];
						newTables[hashi] = cur;
						cur = next;
					}
					_tables[i] = nullptr;
				}
				_tables.swap(newTables);
			}

			size_t hashi = hf(kot(data)) % _tables.size();
			Node* newnode = new Node(data);

			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			++_n;

			return make_pair(iterator(newnode, this, hashi), true);
		}
		iterator Find(const K& key) {
			Hash hf;
			KeyOfT kot;
			size_t hashi = hf(kot(key)) % _tables.size();
			Node* cur = _tables[hashi];

			while (cur) {
				if (kot(cur->_data) == key) {
					return iterator(cur, this, hashi);
				}
				cur = cur->_next;
			}
			return end();
		}

		bool Erase(cosnt K& key) {
			Hash hf;
			KeyOfT kot;
			size_t hashi = hf(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;
				}
				prev = cur;
				cur = cur->_next;
			}
			return false;
		}
	private:
		vector<Node*> _tables;
		size_t _n = 0;
	};

在迭代器部分我们有两个构造函数,其实是为了两个迭代器做准备,因为如果构造函数只有普通版本,在传入const迭代器时,会有权限的放大,会报错,因此我们要实现两套迭代器

然后还需要更改的是每一个T都要用KeyOfT取值才可以进行运算,稍后我们封装成map和set的时候会把哈希函数分别封装进去

UnorderedSet封装

具体实现

namespace xu {
	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, K, SetKeyOfT, Hash>::const_iterator iterator;
		typedef typename hash_bucket::HashTable<K, K, SetKeyOfT, Hash>::const_iterator const_iterator;

		const_iterator begin() const {
			return _ht.begin();
		}

		const_iterator end() const {
			return _ht.end();
		}

		pair<const_iterator, bool> insert(const K& key)
		{
			auto ret = _ht.Insert(key);
			return pair<const_iterator, bool>(const_iterator(ret.first._node, ret.first._pht, ret.first._hashi), ret.second);
		}

		iterator find(const K& key) {
			return _ht.Find(key);
		}

		bool erase(const K& key) {
			return _ht.Erase(key);
		}
	private:
		hash_bucket::HashTable<K, K, SetKeyOfT, Hash> _ht;
	};
}

这里封装倒没有什么难理解的,因为Set不能被更改,所以他的const迭代器是const迭代器,普通迭代器也是const迭代器,因此我们只需要实现const迭代器的部分,即便是普通迭代器,转换到const迭代器也只是权限的缩小,这里是支持的

比较难理解的是插入的这个返回值是什么意思,为什么不能直接返回哈希表里面的插入值呢

因为哈希表本身是支持普通迭代器和const迭代器的,而当set或者map插入时,是完全能成功的,但是在返回时,pair内部只有const迭代器,本质原因是因为普通迭代器不能用于构造const迭代器(因为编译器不认识),注意这里不是简单的类型转换,而是构造

那我们为什么不能直接像之前的红黑树一样直接改成Node*,主要还是因为哈希表的迭代器模板参数更多更加复杂,如果只是Node*是不够用的

我们知道了这个错误的产生原因,就有思路可以解决了,就是不让他自动构造,我们手动构造一个迭代器给她返回就没问题了,也就是使用auto接收插入的返回值,然后再手动构造一个const迭代器,返回,就解决了这里的报错

UnorderMap封装

namespace xu {
	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;

		iterator begin() {
			return _ht.begin();
		}
		iterator end() {
			return _ht.end();
		}
		pair<iterator, bool> insert(const pair<K, V>& kv) {
			return _ht.Insert(kv);
		}
		const V& operator[](const K& key) const {
			pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));
			return ret.first->second;
		}
		V& operator[](const K& key) {
			pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));
			return ret.first->second;
		}
		
		iterator find(const K& key) {
			return _ht.Find(key);
		}
		bool erase(const K& key) {
			return _ht.Erase(key);
		}
	private:
		hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
	};
}

map封装比set封装要简单一些,需要注意的是map支持方括号访问,支持方括号修改,具体原因见介绍map和set那一部分

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Redis面试题三(集群)

目录 1.Redis 集群搭建有几种模式 2.Redis 主从复制的实现 全量同步 增量同步 3.Redis 的主从同步策略 1. 全量同步&#xff08;Full Resynchronization&#xff09; 2. 增量同步&#xff08;Incremental Replication&#xff09; 4.Redis一致性hash 基本原理 节点动态…

使用shared lib将各个构建工具集成到一起

共享库代码 package devopsdef Build(buildType, buildShell){def buildTools ["mvn": "MVN", "ant": "ANT", "gradle": "GRADLE"]println("当前buildType是${buildType}")buildHome tool buildTool…

记内网http洪水攻击,导致网页无法访问一事

事由 最近两日&#xff0c;部分同事在访问税纪云平台时&#xff0c;登录跳转页面频繁转圈、要么就是出现无法连接的错误提示。 无法访问此页面 已重置连接。 请尝试: 检查连接 检查代理和防火墙 运行 Windows 网络诊断经过以下几方面的排查&#xff0c;无果。 后续通过检查…

【C++】从零开始认识泛型编程 — 模版

送给大家一句话&#xff1a; 尽管眼下十分艰难&#xff0c;可日后这段经历说不定就会开花结果。总有一天我们都会成为别人的回忆&#xff0c;所以尽力让它美好吧。 – 岩井俊二 &#xff3c;&#xff3c;\ ⱶ˝୧(๑ ⁼̴̀ᐜ⁼̴́๑)૭兯 //&#xff0f;&#xff0f; &#…

pycharm安装AI写代码插件

在IDE安装特慢&#xff08;可能找不到插件&#xff09; 去官网搜一下 对应安装包 下载zip在IDE解压 插件--已安装齿轮图标--从磁盘里安装 选择下载的插件 应用 --重启OK

3gp转MP4怎么转?简单的3个方法~

3gp最初是由第三代合作伙伴计划&#xff08;3rd Generation Partnership Project&#xff09;设计的&#xff0c;旨在满足移动设备对高效传输音频和视频的需求。它的起源可以追溯到20世纪初&#xff0c;当时移动通信技术的飞速发展使得人们对更加高效的多媒体文件格式有了迫切需…

几种免费SSL证书申请方式

目录 DV单域名免费证书的获取渠道&#xff1a; DV多域名免费证书获取渠道&#xff1a; DV通配符免费证书获取渠道&#xff1a; 随着现在网络安全意识的逐渐提升&#xff0c;越来越多的网站都在相继配对部署SSL证书&#xff0c;用以实现https访问。 大家都知道SSL证书好&…

【linux】基础IO(软硬链接)

上一节我们已经搞懂了已经被打开的文件&#xff0c;还有没有被打开的文件都是怎样被管理起来的&#xff0c;同样&#xff0c;路径的重要性也不言而喻&#xff0c;是确定文件在那个分区&#xff0c;进而可以解析到目标文件与目录内容的关系&#xff0c;从而找到inode&#xff0c…

微带线设计细节的模拟仿真分析

微带线设计在很多PCB设计场景中被应用&#xff0c;但是&#xff0c;有些工程师往往并不注重设计细节&#xff0c;导致最后的设计指标与预期相差甚远&#xff0c;比如设计中&#xff0c;会将丝印、散热金属等放在走线的上方&#xff0c;设计检查时会有人产生质疑&#xff0c;至于…

3DTiles生产流程与规范

一篇19年整理的比较老的笔记了。更多精彩内容尽在数字孪生平台。 瓦片切分 标准的四叉树切分对于均匀分布的地理数据切片非常有效&#xff0c;但是这样均等的切分不适用于随机分布、不均匀分布的地理数据&#xff0c;当地理数据稀疏分布的时候&#xff0c;均等的四叉树就不再高…

java-spring-mybatis -学习第一天-基础知识讲解

目录 前置条件(创建一个项目) Mybatis 定义 可能出现的问题 这边如果连接不上数据库 ​编辑 Dao接口设计 Mybatis流程 创建实体类 User 和其属性 创建Mapper的接口类 测试类测试 实例数据库数据的更新 实例数据库数值的删除 最重要的是有一个原始的数据库 -我这边…

使用 vllm 本地部署 cohere 的 command-r

使用 vllm 本地部署 cohere 的 command-r 0. 引言1. 安装 vllm2. 本地部署 cohere 的 command-r3. 使用 cohere 的 command-r 0. 引言 此文章主要介绍使用 使用 vllm 本地部署 cohere 的 command-r。 1. 安装 vllm 创建虚拟环境&#xff0c; conda create -n myvllm python…

Oracle Linux 8.8 一键安装 Oracle 11GR2 RAC(231017)

前言 Oracle 一键安装脚本&#xff0c;演示 Oracle Linux 8.8 一键安装 Oracle 11GR2 RAC&#xff08;231017&#xff09;过程&#xff08;全程无需人工干预&#xff09;&#xff1a;&#xff08;脚本包括 ORALCE PSU/OJVM 等补丁自动安装&#xff09; ⭐️ 脚本下载地址&…

kafka大数据采集技术实验(未完待续)

Kafka环境搭建 下载地址&#xff1a;https://link.zhihu.com/?targethttps%3A//kafka.apache.org/downloads解压启动zookeeper bin/zookeeper-server-start.sh config/zookeeper.properties需要注意的是 : " c o n f i g / z o o k e e p e r . p r o p e r t i e s &q…

维态思(上海)环保科技有限公司 | 2024全国水科技大会暨技术装备成果展览会

嘉宾简介 胡建龙 维态思&#xff08;上海&#xff09;环保科技有限公司 总经理 报告题目&#xff1a;微生态滤床 植物工厂——小城镇生活污水生态净化及零排放案例分享 国家注册设备工程师&#xff08;给排水&#xff09;、上海市&#xff08;合作交流&#xff09;五四青年…

BUUCTF---misc---[ACTF新生赛2020]outguess

1、下载附件&#xff0c;解压之后得到下面信息 2、查看图片属性&#xff0c;发现有个核心价值观编码&#xff1b;解码为abc 3、flag.txt提示 4、结合题目&#xff0c;这是一个outguess隐写 5、用kali先下载安装隐写库 6、使用命令-k(密钥)&#xff1b;-r(将图片里面的隐写信息…

InstantMesh:利用稀疏视图大规模重建模型从单张图像高效生成3D网格

作者&#xff1a;Jiale Xu&#xff0c;Weihao Cheng&#xff0c;Yiming Gao等 编译&#xff1a;东岸因为一点人工一点智能 InstantMesh&#xff1a;利用稀疏视图大规模重建模型从单张图像高效生成3D网格在这项工作中&#xff0c;我们提出了InstantMesh&#xff0c;一个开源的…

免费在英伟达官网使用多个开源AI大模型

英伟达官网能体验到多个聊天AI和图片生成AI&#xff0c;不废话直接上链接 AI开源大模型&#xff08;https://build.nvidia.com/explore/discover?api-keytrue&#xff09; 开源的AI大模型有meta的llama3-8b和llama3-70b、snowflake的arctic、microsoft的phi-3-mini、mistral…

【Linux系统编程】第九弹---权限管理操作(下)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】 目录 1、目录权限 2、粘滞位 总结 1、目录权限 首先提出一个问题&#xff0c;删除一个文件需要什么权限呢&#xff1f;&#xff1f…

虚拟机软件哪个好用 虚拟机软件哪个可以玩暗区突围 虚拟机软件排名 PD19虚拟机 Mac类虚拟机运行Windows程序 CrossOver支持的热门游戏

随着跨系统互联的需求不断增长&#xff0c;越来越多的用户会选择在电脑系统中安装虚拟机软件&#xff0c;进而更加便捷地访问和操作其他系统。一款好用的虚拟机软件能够提高系统互联的效率&#xff0c;进而实现了资源共享、测试环境搭建等多种用途。而在众多的虚拟机软件当中&a…
最新文章