【C++进阶】用哈希实现unordered_set和unordered_map的模拟

news2024/11/26 10:04:48

🪐🪐🪐欢迎来到程序员餐厅💫💫💫

          主厨:邪王真眼

主厨的主页:Chef‘s blog  

所属专栏:c++大冒险
 

总有光环在陨落,总有新星在闪烁


前言:

        之前我们学完红黑树后对他进行了改造,使之成为map和set的底层容器,今天我们则要把哈希表进行修改并以此为基础实现unordered_set和unordered_map

一.哈希桶(修改版)

1.1.节点

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

注意事项:

  • 数据类型是T,因为要同时适用unordered_set(存储键值)和unordered_map(存储键值对

类比咱们用红黑树写map和set时的做法。

1.2.迭代器

哈希表的迭代器类型是单向迭代器,没有反向迭代器

1.2.1成员变量

template<class K, class T, class KeyOfT, class HF>
class HashTable;
template<class K,class T, class Ref,class Ptr,class KeyOfT, class HF>
struct HashIterator
{
	typedef HashNode<T> Node;
	typedef HashIterator<K, T, Ref, Ptr, KeyOfT, HF> self;
	typedef HashIterator<K, T, T&, T*, KeyOfT, HF> iterator;
	typedef HashTable<K, T, KeyOfT, HF> HT;
	Node* _node;
	HT* _ht;
};

注意事项:

  •  增加了_ht成员变量,因为在重载++时,当一条单链表走到空,则需要走到下一个哈希桶的位置,需要通过哈希表的vector成员找下一个位置 
  • 因为HashTable是在后面实现的,所以我们要先写一个声明

1.2.2简单函数实现

HashIterator(Node*&node=nullptr,Node*&ht=nullptr)
	:_node(node)
	,_ht(ht)
{}
HashIterator(const iterator&it)
	:_node(it._node)
	, _ht(it._ht)
{}
Ref& operator*()const
{
	return _node->_data;
}
Ptr& operator->()const
{
	return &(_node->_data);
}
bool operator==(const self&se)
{
	return _node == se._node;
}
bool operator!=(const self& se)
{
	return _node != se._node;
}

注意事项:

  • 拷贝构造函数可以以普通迭代器拷贝出普通迭代器(普通迭代器调用时)以及const迭代器(const迭代器调用时)

1.2.3operator++

self& operator++()
{
	Node* node = _node;
	if (node->_next)
	{
		_node = node->_next;
		return *this;
	}
	else
	{
		KeyOfT kot;
		size_t hash = HF(kot(_node->_data)) % _ht->_tables.size()+1;
		for (hash; hash < _ht->_tables.size(); hash++)
		{
			if (_ht->tables[hash])
			{
				_node = _ht->tables[hash];
				return *this;
			}

		}
		_node = nullptr;
		return *this;
	}
}
self operator++(int)
{
	self new_self=*this;
	(*this)++;
	return new_self;
}

思路:

  1. 前置++的思路: 
    1. 下一个结点不为空,则跳到下一位
    2. 下一个结点为空,则先取模算出哈希地址,再往后探测不为空的哈希桶 
  2. 后置++:复用前置++,返回临时对象

1.3哈希桶本身

1.3.1成员变量

template<class K, class T, class KeyOfT, class HF>
class HashTable
{
	template<class K, class T, class Ref, class Ptr, class KeyOfT, class HF>
	friend struct HashIterator;
	typedef HashNode<T> Node;
public:
protected:
	vector<Node*> _tables;
	size_t _size = 0;//有效数据个数
};

注意事项:

  1. 迭代器要访问哈希桶的私有成员,所以要声明友元
  2. 模板参数KeyOfT是为了从类型T中取出键值
  3. 模板参数HF即是HashFunc,哈希函数,用于辅助键值转化为整型进行取模操作

1.3.2构造函数和析构函数

HashTable(size_t size = 10)
{
	_tables.resize(size);
}
~HashTable()
{
	for (auto hash_node : _tables)
	{
		while (hash_node)
		{
			Node* new_node = hash_node->_next;
			delete hash_node;
			hash_node = new_node;
		}
	}
}

1.3.3 begin和end 

typedef HashIterator< K,T, T&, T*,  KeyOfT,  HF> iterator;
typedef HashIterator< K, T, const T&, const T*,  KeyOfT,  HF> const_iterator;
iterator begin()
{
	for (size_t i = 0; i < _tables.size(); i++)
		if (_tables[i])
			return iterator(_tables[i], this);
	return iterator(nullptr, this);
}
const_iterator begin()const
{
	for (size_t i = 0; i < _tables.size(); i++)
		if (_tables[i])
			return const_iterator(_tables[i], this);
	return const_iterator(nullptr, this);
}
iterator end()
{
	return iterator(nullptr, this);
}
const_iterator end()
{
	return const_iterator(nullptr, this);
}

注意事项:

  1. begin返回最开始不为空的哈希桶的迭代器,而不是最开始的哈希桶的迭代器
  2. end返回空迭代器
  3. 构造迭代器需要传入哈希表本身的地址,这里直接传this指针即可

1.3.4Find函数 

iterator Find(const K&key)
{
	HF hf;
	KeyOfT kot;
	size_t hash = hf(key) % _tables.size();
	Node* cur = _tables[hash];
	while (cur)
	{
		if (kot(cur->_data) == key)
			return iterator(cur,_tables);
		cur = cur->_next;
	}
	return iterator(nullptr,_tables);
}

注意事项:

  1. 返回值类型是迭代器
  2. 运用两个仿函数,,kot负责获取存储的键值,hf作为哈希函数把键值key转整型

1.3.5   Insert函数 

	pair<iterator,bool> Insert(T& data)
{
	KeyOfT kot;
	HF hf;
	iterator it = Find(kot(data));
	if (it._node)
	{
		return make_pair(it, false);
	}
	if (_size == _tables.size())
	{
		vector<Node*> new_tables(_size * 2);
		for (auto node : _tables)
		{
			while (node)
			{
				Node* next = node->_next;
				size_t hash = hf(kot(node->_data)) % new_tables.size();
				node->_next = new_tables[hash];
				new_tables[hash] = node;
				node = next;
			}
		}
		_tables.swap(new_tables);
	}
	
	size_t hash = hf(kot(data)) % _tables.size();
	Node* cur = _tables[hash];
	Node* p(data);
	p->_next = cur;
	_tables[hash] = p;
	_size++;
	return make_pair(iterator(p,this),true);
}

注意事项:

  1. 返回值类型是pair,第一个参数为迭代器,第二个参数为布尔值(记录是否插入成功)
  2. 运用两个仿函数,,kot负责获取存储的键值,hf作为哈希函数把键值key转整型

1.3.6Erase函数 

	bool Erase(const K& key)
{
	HF hf;
	KeyOfT kot;
	size_t hash = hf(key) % _tables.size();
	Node* cur = _tables[hash];
	Node* pre = nullptr;
	while (cur)
	{
		if (kot(cur->data) == key)
			break;
		pre = cur;
		cur = cur->_next;
	}
	if (cur == nullptr)
		return false;
	_size--;
	if (pre == nullptr)
		_tables[hash] = cur->_next;
	else
		pre->_next = cur->_next;
	delete cur;
	return true;
}

二、unordered_set

2.1成员变量及仿函数 

template<class K,class HF=HashFunc<K>>
class unordered_set
{
public:
	struct SetKeyOfT
	{
		K& operator()(const K& key)
		{
			return key;
		}
	};
protected:
	HashTable<K, K, SetKeyOfT, HF> _hf;
};

注意事项:

  •  1.这里的数据存储类型就是键值Key本身,所以SetKeyOfT直接返回key就行
  • 2.哈希函数不是固定的,可以根据需求自己进行实现并传到给定模板参数中

2.2 begin和end 

typedef typename HashTable<K, K, SetKeyOfT, HF>::iterator iterator;
typedef typename HashTable<K, K, SetKeyOfT, HF>::const_iterator const_iterator;
iterator begin()
{
	return _ht.begin();
}
const_iterator begin()const
{
	return _ht.begin();
}
iterator end()
{
	return _ht.end();
}
const_iterator end()const
{
	return _ht.end();
}

注意事项:

  • 在C++中,编译器默认iterator为变量名,如果作为类型名,需要在前面加上typename加上typename关键字。
  • unordered_set中存储的键值key不允许修改,所以其普通迭代器和const迭代器均为哈希表的const迭代器
  • 我们称set的begin为A,哈希的begin为B,A的实现是对B的调用,在A的参数是普通迭代器时,B也是普通迭代器,B的返回值也是普通迭代器,但A的返回值是const迭代器,所以这里需要用普通迭代器创建一个const迭代器的临时变量,这就用到之前写的拷贝构造函数了。

2.3Find 

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

注意事项: 

  •  此时也有从普通迭代器到const迭代器的转换 

2.4Insert

pair<iterator, bool> Insert(const K& key)
{
	return _ht.Insert(key);
}

注意事项: 

  1. 函数形参类型是K
  2. 此时也有从普通迭代器到const迭代器的转换

 2.5Erase

bool Erase(const K& key)
{
	return _ht.Erase(key);
}

三、unordered_map

unordered_map和unordered_set的实现有众多相似之处,

3.1 成员变量与仿函数

template<class K,class V,class HF=HashFunc<K>>
class unordered_map
{
	struct MapKeyOfT
{
	const K& operator()(const pair<K, V>&kv)
	{
		return kv.first;
	}
	};
protected:
	HashTable<K, pair<const K, V>, MapKeyOfT, HF> _ht;
};

注意事项:

  • 1.这里节点存的数据类型是pair<K,V>,我们的MapKeyOfT作用是返回其中的键值key 
  • 2.哈希函数不是固定的,可以根据需求自己进行实现并传到给定模板参数中

3.2 begin和end

typedef typename HashTable<K, pair<K,V>, MapKeyOfT, HF>::iterator iterator;
typedef typename HashTable<K, pair<K,V>, MapKeyOfT, HF>::const_iterator const_iterator;
iterator begin()
{
	return _ht.begin();
}
const_iterator begin()const
{
	return _ht.begin();
}
iterator end()
{
	return _ht.end();
}
const_iterator end()const
{
	return _ht.end();
}

注意事项:

  1. 加上typename关键字,编译器才能识别类型
  2. unordered_map不允许修改key,故普通迭代器和const的pair中的K都要加上const修饰,但是允许修改存储的data,所以普通和const迭代器一一对应(好好想想这点map和set的处理差异)

3.3 find

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

3.4Insert

	pair<iterator, bool> Insert(const pair<const K, V>& kv)
	{
		return _ht.Insert(kv);
	}

注意事项:

  • 形参类型是键值对而不是键值

3.5 operator[ ]

V& operator[](const& K key)
{
	return (*(_ht.Insert(make_pair(key, V()))).first).second;
}
//或者你也可以这么写
V& operator[](const K& key)
{
	pair<iterator, bool> cur = insert(make_pair(key, V()));
	return cur.first->second;
}

注意事项:

  1.  利用operator[]进行插入和修改操作是很方便的,所以要学会灵活运用哦 
  2. 返回值是value的引用,我们可以直接进行修改 

3.6 Erase 

bool Erase(const K& key)
{
	return _ht.Erase(key);
}

 


 如果你对该系列文章有兴趣的话不妨点个关注哦,我会持续更新相关博客的

🥰创作不易,你的支持对我最大的鼓励🥰

🪐~ 点赞收藏+关注 ~🪐


 

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

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

相关文章

Golang中的上下文-context包的简介及使用

文章目录 简介context.Background()上下文取消函数上下文值传递建议Reference 简介 Go语言中的context包定义了一个名为Context的类型&#xff0c;它定义并传递截止日期、取消信号和其他请求范围的值&#xff0c;形成一个链式模型。如果我们查看官方文档&#xff0c;它是这样说…

【LeetCode: 572. 另一棵树的子树 + 二叉树 + dfs】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

Qt编译QScintilla(C++版)过程记录,报错-lqscintilla2_qt5d、libqscintilla2_qt5找不到问题解决

Qt编译QScintilla [C版] 过程记录 本文是编译该 QScintilla 组件库供 QtCreater 开发 C 桌面软件 流程记录一、编译环境 系统&#xff1a; Windows 10Qt&#xff1a;Qt 5.14.2编译套件&#xff1a;MinGW 64Qscintilla&#xff1a;QScintilla_src-2.11.6 二、下载链接 网站链…

「51媒体网」邀请媒体采访报道对企业宣传有何意义?

传媒如春雨&#xff0c;润物细无声的&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 邀请媒体采访报道对企业宣传具有多重意义&#xff1a; 提升品牌知名度和曝光度&#xff1a;媒体是信息传播的重要渠道&#xff0c;通过媒体的报道&#xff0c;企业及其活动、产品能够迅…

uniapp:Hbuilder没有检测到设备请插入设备或启动模拟器的问题解决

问题 使用模拟器调试运行项目时&#xff0c;出现以下提示&#xff0c;“没有检测到设备&#xff0c;请插入设备或启动模拟器后点击刷新再试”。排查了一天最终找到原因。 解决 已确认模拟器是已经正常启动&#xff0c;并且Hbuilder设置中的adb路径和端口都配置没有问题&#…

主从复制、数据持久化 、Redis主从集群、哨兵机制 、Redis分片集群

数据持久化 Redis、主从集群、哨兵机制 Redis分片集群 1、单点 redis 的问题2、主从复制2.1 命令传播 3、Redis的持久化3.1 AOF3.2 RDB&#xff08;默认方式&#xff09;RDB 方式&#xff1a;执行快照时&#xff0c;数据能被修改吗&#xff1f;RDB 方式总结 3.3 RDB 和 AOF 组合…

LC低通滤波

LC滤波器&#xff0c;是指将电感L与电容器 C进行组合设计构成的滤波电路&#xff0c;可去除或通过特定频率的无源器件。电容器具有隔直流通交流&#xff0c;且交流频率越高越容易通过的特性。而电感则具有隔交流通直流&#xff0c;且交流频率越高越不易通过的特性。因此&#x…

【简单讲解下PHP AES加解密示例】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

k8s_入门_kubelet安装

安装 在大致了解了一些k8s的基本概念之后&#xff0c;我们实际部署一个k8s集群&#xff0c;做进一步的了解 1. 裸机安装 采用三台机器&#xff0c;一台机器为Master&#xff08;控制面板组件&#xff09;两台机器为Node&#xff08;工作节点&#xff09; 机器的准备有两种方式…

知网参考文献引用格式转latex中BibTex-Python操作

处理思路 参考 处理步骤&#xff1a; &#xff08;单条处理&#xff1a;&#xff09; 1、选知网NoteExpress格式的2-7行复制信息 2、新建一个文本文件&#xff0c;命名为cite.txt&#xff0c;把知网所复制信息粘贴进来 &#xff08;txt文件保存编码ANSI可行&#xff09; 3、…

【Linux进阶之路】地址篇

文章目录 一、ipv4地址1. 基本概念2. 分类3.CIDR4.特殊的ip地址 二、IP协议1. 协议字段2.分片与重组3.路由 三、NAT技术1.公有和私有2.NAT3.NAPT 四、ARP协议1.MAC地址2.ARP 五、DHCP协议六、DNS协议尾序 一、ipv4地址 1. 基本概念 概念&#xff1a;IP地址&#xff0c;英文全…

针对序列任务—transformer

文章目录 针对序列任务self-attention(注意力机制)流程multi-head self-attention(多头注意力机制)流程positional encoding(位置编码)TransformerembeddingAdd & Normfeed forwardMasked 李老师官方视频传送门 李老师课程主页传送门 针对序列任务 RNN无法实现并行化操作…

网络安全 | 什么是区块链?

关注WX&#xff1a;CodingTechWork 概述 定义 区块链是一个共享的、不可篡改的账本&#xff0c;旨在促进业务网络中的交易记录和资产跟踪流程。资产可以是有形的&#xff08;如房屋、汽车、现金、土地&#xff09;&#xff0c;也可以是无形的&#xff08;如知识产权、专利、…

吴恩达机器学习理论基础解读

吴恩达机器学习理论基础 机器学习最常见的形式监督学习&#xff0c;无监督学习 线性回归模型概述 应用场景一&#xff1a;根据房屋大小预测房价 应用场景二&#xff1a;分类算法&#xff08;猫狗分类&#xff09; 核心概念&#xff1a;将训练模型的数据称为数据集(学习数据…

创建一个C# WinForm应用程序的步骤

创建项目界面设计设置属性编写代码保存项目运行程序 1. 新建项目 默认情况下&#xff0c;项目名称和解决方案名称是保持一致的&#xff0c;用户也可以修改成不一样的。一个解决方案下面是可以包含多个项目的&#xff0c;比如和应用程序相关的数据结构项目、一些资源等。 点击…

2024/4/1—力扣—删除字符使频率相同

代码实现&#xff1a; 思路&#xff1a; 步骤一&#xff1a;统计各字母出现频率 步骤二&#xff1a;频率从高到低排序&#xff0c;形成频率数组 步骤三&#xff1a;频率数组只有如下组合符合要求&#xff1a; 1, 0...0n 1, n...n (, 0)n...n, 1(, 0) bool equalFrequency(char…

ubuntu安装

一、安装虚拟机 https://www.vmware.com/products/workstation-pro/workstation-pro-evaluation.html 下载后运行安装向导&#xff0c;一直Next即可 许可证&#xff1a; https://zhuanlan.zhihu.com/p/685829787#:~:textpro,17%E5%AF%86%E9%92%A5%EF%BC%9AMC60H-DWHD5-H80U9-6…

day5 nest商业项目初探·一(java转ts全栈/3R教室)

背景&#xff1a;从头一点点学起太慢了&#xff0c;直接看几个商业项目吧&#xff0c;看看根据Java的经验&#xff0c;自己能看懂多少&#xff0c;然后再系统学的话也会更有针对性。先看3R教室公开的 kuromi 移民机构官方网站吧 【加拿大 | 1.5w】Nextjs&#xff1a;kuromi 移民…

AI应用实战1:AI项目实战五大环节

文章目录 环节一&#xff1a;定义问题环节二&#xff1a;收集和处理数据环节三&#xff1a;选择机器学习模型环节四&#xff1a;训练模型环节五&#xff1a;超参数调试和性能优化1.评价模型效果的指标分类任务评估标准&#xff1a;回归任务评估标准&#xff1a;其他通用评估指标…

分公司=-部门--组合模式

1.1 分公司不就是一部门吗&#xff1f; "我们公司最近接了一个项目&#xff0c;是为一家在全国许多城市都有分销机构的大公司做办公管理系统&#xff0c;总部有人力资源、财务、运营等部门。" "这是很常见的OA系统&#xff0c;需求分析好的话&#xff0…