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

news2024/11/25 12:22:56

文章目录

  • 前言
  • 一、模板参数的改造
  • 二、模板的特例化操作
  • 三、仿函数的妙用
  • 四、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);
			//先拿到为普通迭代器的iterator
			return 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.coloradmin.cn/o/1029544.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

340. 至多包含 K 个不同字符的最长子串

340. 至多包含 K 个不同字符的最长子串 vip

Michael.W基于Foundry精读Openzeppelin第36期——Ownable2Step.sol

Michael.W基于Foundry精读Openzeppelin第36期——Ownable2Step.sol 0. 版本0.1 Ownable2Step.sol 1. 目标合约2. 代码精读2.1 pendingOwner() && transferOwnership(address newOwner) && _transferOwnership(address newOwner)2.2 acceptOwnership() 0. 版本 …

YOLOv8快速复现 训练 SCB-Dataset3-S 官网版本 ultralytics

目录 0 相关资料SCB-Dataset3-S 数据训练yaml文件 YOLOv8 训练SCB-Dataset3-S相关参数 0 相关资料 YOLOV8环境安装教程.&#xff1a;https://www.bilibili.com/video/BV1dG4y1c7dH/ YOLOV8保姆级教学视频:https://www.bilibili.com/video/BV1qd4y1L7aX/ b站视频&#xff1a;…

第一百五十回 自定义组件综合示例:游戏摇杆

文章目录 概念介绍实现方法示例代码我们在上一章回中介绍了自定义组件相关的内容,本章回中将综合使用这些内容 自定义游戏摇杆组件.闲话休提,让我们一起Talk Flutter吧。 概念介绍 我们介绍的游戏摇杆就是一个内层的小圆嵌套一个外层的大圆,大圆的位置不变,小圆只能在大圆…

【AD9361】设置带宽

接收链路滤波器 RX TIA LPF&#xff1a;RX TIA LPF 是一款单极点低通滤波器&#xff0c;具有可编程 3dB 转角频率。转折频率可在 1 MHz 至 70 MHz 范围内进行编程。RX TIA LPF 通常校准为基带通道带宽的 2.5 倍。 RX BB LPF&#xff1a;RX BB LPF 是具有可编程 3dB 转角频率的…

2、Window上的 虚拟机端口 暴露到 宿主机局域网教程

今天在公司的服务器主机上捣鼓虚拟机&#xff0c;要在虚拟机上安装一个oracle&#xff0c;虚拟机和主机能互相ping通的前提下&#xff0c;要将虚拟机上的端口号暴露在主机上&#xff0c;让项目组内的所有员工的电脑都能访问到该oracle数据库。 也就是电脑A 访问主机&#xff0…

Linux -- 使用多张gpu卡进行深度学习任务(以tensorflow为例)

在linux系统上进行多gpu卡的深度学习任务 确保已安装最新的 TensorFlow GPU 版本。 import tensorflow as tf print("Num GPUs Available: ", len(tf.config.list_physical_devices(GPU)))1、确保你已经正确安装了tensorflow和相关的GPU驱动&#xff0c;这里可以通…

Scala 高阶:Scala中的模式匹配

一、概述 Scala中的模式匹配&#xff08;case&#xff09;类似于Java中的switch...case&#xff0c;但是Scala的模式匹配功能更为强大。通过模式匹配&#xff0c;可以匹配更复杂的条件和数据结构&#xff0c;包括常量、类型、集合、元组等。而 Java 的 switch 语句只能用于匹配…

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

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

新建excel出现由于找不到vcruntime140_1.dll,无法继续执行代码。系统错误

打开excel报错&#xff0c;提示缺少​​vcruntime140_1D.dll​​。 那解决方法无疑就是找到这个​​DLL​​​&#xff0c;然后放到电脑系统中。 在网站​中搜索​vcruntime140_1.dll&#xff0c;下载 把你下载的文件放到系统中&#xff0c;把dll文件放到​​C:\Windows\Syst…

2、Window上的虚拟机暴露到宿主机局域网教程

今天在公司的服务器主机上捣鼓虚拟机&#xff0c;要在虚拟机上安装一个oracle&#xff0c;虚拟机和主机能互相ping通的前提下&#xff0c;要将虚拟机上的端口号暴露在主机上&#xff0c;让项目组内的所有员工的电脑都能访问到该oracle数据库。 也就是电脑A 访问主机&#xff0…

1997-2023年1月中国企业跨国并购数据Zephyr数据库

1997-2023年1月中国企业跨国并购数据 1、时间&#xff1a;1997-2023年1月 2、来源&#xff1a;根据Zephyr数据库整理形成 3、样本量&#xff1a;3.6万 4、指标&#xff1a;变量信息全面&#xff0c;包括交易的详细时间、金额、标的、介绍、行业等等具体如下&#xff1a; D…

再也不怕面试官拷打Go数据结构了!-Go语言map详解

map是Go语言中的哈希表&#xff0c;用来存储key/value键值对。可以在O(1)的时间复杂度内进行查找。map是Go语言在面试中经常会被问到的一个点&#xff0c;这篇文章就来细细梳理一下map的相关知识点&#xff0c;面试不再怕&#xff0c;offer顶呱呱。 哈希冲突 我们知道&#x…

【每日一题】74. 搜索二维矩阵

74. 搜索二维矩阵 - 力扣&#xff08;LeetCode&#xff09; 给你一个满足下述两条属性的 m x n 整数矩阵&#xff1a; 每行中的整数从左到右按非递减顺序排列。每行的第一个整数大于前一行的最后一个整数。 给你一个整数 target &#xff0c;如果 target 在矩阵中&#xff0c;返…

探索 Java JDK 21 的新特性:一场深度解析之旅

探索 Java JDK 21 的新特性&#xff1a;一场深度解析之旅 探索 Java JDK 21 的新特性&#xff1a;一场深度解析之旅摘要 &#x1f680;引言 &#x1f310;正文 &#x1f4da;新特性概览 &#x1f195;1. 模式匹配的魅力 &#x1f9e9;2. 记录类型&#xff1a;轻松管理数据 &…

【深度学习】ONNX模型多线程快速部署【基础】

【深度学习】ONNX模型CPU多线程快速部署【基础】 提示:博主取舍了很多大佬的博文并亲测有效,分享笔记邀大家共同学习讨论 文章目录 【深度学习】ONNX模型CPU多线程快速部署【基础】前言搭建打包环境python多线程并发简单教程基本教程ONNX模型多线程并发 打包成可执行文件总结 前…

王道考研计算机组成原理

王道考研计算机组成原理 计算机系统概述计算机系统层次结构计算机的性能指标错题 数据的表示和运算数制与编码运算方法和运算电路浮点数的表示与运算 存储系统存储器概述主存储器主存储器与CPU的连接外部存储器高速缓冲存储器虚拟存储器 指令系统指令格式指令的寻址方式程序的机…

js加密双重保障之sm2国密

前言 ​ 最近看到一些项目里边有用到sm2/3/4国密加密算法&#xff0c;这里给大家简单介绍一下。 知识科普 SM2&#xff08;国密算法&#xff09;是一种非对称加密算法&#xff0c;由中国国家密码管理局&#xff08;NCC&#xff09;制定&#xff0c;并被广泛应用于中国的信息…

9月21日作业

登录代码&#xff1a; widget.h #ifndef REGISTER_H #define REGISTER_H#include <QWidget> #include <QDebug> #include <QSqlDatabase> #include <QSqlQuery> #include <QMessageBox>namespace Ui { class Register; }class Register : publ…