【C++】unordered_map/set实现(哈希)

news2024/11/28 15:09:41

         1 .模板参数列表的改造

  • unordered_set是 K模型 的容器,unordered_map是 KV模型 的容器。
  • 要想只用一份哈希表代码同时封装出K模型和KV模型的容器,我们就要对哈希表的模板参数进行控制。
  • 为了与原哈希表的模板参数进行区分,这里将哈希表的第二个模板参数的名字改为T。
template<class K, class V, class KeyOfValue, class HF = DefHashF<T> >
class HashBucket;
  •  K:关键码类型
  • V: 不同容器V的类型不同,如果是unordered_map,V代表一个键值对,如果是unordered_set,V 为 K

unordered_set:

template<class K>
class unordered_set
{
   
public:
	//...
private:
//传入底层哈希表的是K和K
	HashTable<K,const K> _ht; 
};

unordered_map :注意这里V里面的K要加const,因为K是不可更改的

template<class K, class V>
class unordered_map
{
   
public:
	//...
private:
	HashTable<K, pair<const K, V>> _ht; //传入底层哈希表的是K以及K和V构成的键值对
};

  • KeyOfValue: 因为V的类型不同,通过value取key的方式就不同,详细unordered_map/set的实现
  • HF: 哈希函数仿函数对象类型,哈希函数使用除留余数法,需要将Key转换为整形数字才能取模

         2 .增加迭代器

迭代器的结构:

有两个成员,一个是节点指针,还有一个是哈希表的指针,只要就是为了方便实现迭代器++遍历哈希表的操作。

模板参数列表的前四个class主要是为了实现普通迭代器和const迭代器,第五个参数就是为了获得T中的key值,是一个仿函数,最后一个模板参数是哈希函数,为了构造出哈希表指针而存在

迭代器基本操作的实现:

template<class K,class T,class Ref,class Ptr,class KeyOfT,class hash>
struct HashIterator
{
//重命名哈希节点
	typedef HashData<T> Node;
//重命名迭代器
	typedef HashIterator<K,T, Ref, Ptr, KeyOfT, hash> self;

//指向哈希节点的指针
	Node* _node;
//指向哈希表的指针
	HashTable<K, T, KeyOfT, hash>* _ht;

	HashIterator(Node* node, HashTable<K, T, KeyOfT, hash>*ht) :_node(node),_ht(ht) {}

//解引用
	Ref operator*()
	{
		return _node->_data;
	}
//重载箭头
	Ptr operator->()
	{
		return &_node->_data;
	}
//前置加加
	self& operator++()
	{
		if (_node->_next)
			_node = _node->_next;
		else
		{
			hash hs;
			KeyOfT kot;
			int hashi = hs(kot(_node->_data)) % _ht->_tables.size();
			int i = hashi + 1;
			for (; i < _ht->_tables.size(); i++)
			{
				if (_ht->_tables[i])
				{
					_node = _ht->_tables[i];
					break;
				}
			}
			if(_ht->_tables.size() == i)
			_node = nullptr;
		}
		return *this;
	}
//重载!=
	bool operator!=(const self& l)
	{
		return _node != l._node;
	}

};

哈希表内部改造:

template<class K,class T,class KeyOfT,class hash>
class HashTable
{
//注意这里需要声明友元才行
//因为迭代器里面有指向哈希表的指针
//这里可以采用内部内的方法
	friend HashIterator<K, T, T&, T*, KeyOfT, hash>;
public:
//普通迭代器
	typedef HashIterator<K, T, T&, T*, KeyOfT, hash> iterator;
//const迭代器
	typedef HashIterator<K, T,const T&,const T*, KeyOfT, hash> const_iterator;
//节点指针
	typedef HashData<T> Node;
//迭代器
	HashTable(int n = 10)
	{
		_tables.resize(n,nullptr);
	}
	iterator begin()
	{
		for (int i = 0; i < _tables.size(); i++)
		{
			if (_tables[i])
				return iterator(_tables[i],this);
		}
		return iterator(nullptr,this);
	}
	const_iterator begin()const
	{
		for (int i = 0; i < _tables.size(); i++)
		{
			if (_tables[i])
				return iterator(_tables[i], this);
		}
		return iterator(nullptr, this);
	}
	iterator end()
	{
		return iterator(nullptr, this);
	}	
	const_iterator end()const
	{
		return iterator(nullptr, this);
	}
//插入
	std::pair<iterator,bool> insert(const T& data)
	{
		KeyOfT kot;
		iterator ret = Find(kot(data));
		if(ret!=end())//这里的!=重载的时候必须加const
			return std::make_pair(ret, false);
		//哈希桶不需要线性探测,挂过去就行了
		hash hs;
		Node* newnode = new Node(data);
		int hashi = hs(kot(data)) % _tables.size();
		newnode->_next = _tables[hashi];
		_tables[hashi] = newnode;
		_n++;
		//判断是否需要扩容
		if (_n * 10 / _tables.size()  > 8)
		{
			HashTable<K,T,KeyOfT,hash> newHT(_tables.size()*2);
			//采用挪动节点的方法,减少消耗
			for (auto& head : _tables)
			{
				Node* cur = head;
				while (cur)
				{
					Node* next = cur->_next;
					int hashi = hs(kot(cur->_data)) % newHT._tables.size();
					cur->_next = newHT._tables[hashi];
					newHT._tables[hashi] = cur;
					cur = next;
				}
				head = nullptr;
			}
			_tables.swap(newHT._tables);
		}
		return std::make_pair(iterator(newnode, this), true);
	}
	iterator Find(const K& data)
	{
		hash hs;
		KeyOfT kot;
		int hashi = hs(data) % _tables.size();
		if (_tables[hashi])
		{
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (kot(cur->_data) == data)return iterator(cur,this);
				cur = cur->_next;
			}
		}
		return iterator(nullptr,this);
	}
//删除
	bool Erase(const K& key)
	{
		hash hs;
		KeyOfT kot;
		int hashi = hs(key) % _tables.size();
		if (_tables[hashi])
		{
			Node* cur = _tables[hashi];
			Node* parent = nullptr;
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					if (parent)
						parent->_next = cur->_next;
					else
						_tables[hashi] = cur->_next;
					delete cur;
					cur = nullptr;
					--_n;
					return true;
				}
				parent = cur;
				cur = cur->_next;
			}
			return false;
		}
	}
//析构
	~HashTable()
	{
		for (auto head : _tables)
		{
			Node* cur = head;
			while (cur)
			{
				Node* next = cur->_next;
				delete cur;
				cur = next;
			}
			head = nullptr;
		}
	}
private:
//这里用vector来存储节点(节点是用链表存储
	std::vector<HashData<T>*> _tables;
	int _n = 0;
};

         3 . unordered_map

封装unordered_map:

template<class K>
//哈希函数,很多类型需要自己实现
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return size_t(key);
	}
};
//string经常使用,搞一个特化版本
template<>
struct HashFunc<std::string>
{
	size_t operator()(const std::string& str)
	{
		int hash = 0;
		for (auto e : str)
		{
			hash *= 131;
			hash += e;
		}
		return hash;
	}
};
//使用unordered_map需要传入哈希函数,也有类型是默认的
template<class K, class V, class hash = HashFunc<K>>
class unordered_map
{

public:
//用来取比较大小的key
	struct KeyOfT
	{
		const K& operator()(const std::pair<const K, V>& d)
		{
			return d.first;
		}
	};
	typedef typename HashTable<K, std::pair<const K, V>, KeyOfT, hash>::iteratiterator;
//这里不认识iterator是因为没有实例化
//编译器不知道他是类还是什么
//加tepename是为了告诉编译器他是一个类型
	typedef typename HashTable<K, const K, KeyOfT, hash>::const_iterator const_iterator;
	iterator begin()
	{
		return _ht.begin();
	}
	iterator end()
	{
		return _ht.end();
	}
	const_iterator begin()const
	{
		return _ht.begin();
	}
	const_iterator end()const
	{
		return _ht.end();
	}
//实现插入
	std::pair<iterator, bool> insert(const std::pair<K, V>& data)
	{
		auto ret = _ht.insert(data);
		return ret;
	}
	bool erase(const K& key)
	{
		return _ht.Erase(key);
	}
//重载[]
//没有就插入并返回——值
//有就返回——值
	V& operator[](const K& key)
	{
		auto ret = _ht.insert(std::make_pair(key, V()));
		return ret.first->second;
	}
//查找返回迭代器
	iterator find(const K& key)
	{
		return _ht.Find(key);
	}
private:
	HashTable<K, std::pair<const K, V>, KeyOfT, hash> _ht;
};

         4 . unordered_set

封装unordered_set:与上面unordered_map类似

template<class K>
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return size_t(key);
	}
};
template<>
struct HashFunc<std::string>
{
	size_t operator()(const std::string& str)
	{
		int hash = 0;
		for (auto e : str)
		{
			hash *= 131;
			hash += e;
		}
		return hash;
	}
};
template<class K, class hash = HashFunc<K>>
class unordered_set
{
	struct KeyOfT
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
public:
	typedef typename HashTable<K, const K, KeyOfT, hash>::iterator iterator;
	typedef typename HashTable<K, const K, KeyOfT, hash>::const_iterator const_iterator;
	iterator begin()
	{
		return _ht.begin();
	}
	iterator end()
	{
		return _ht.end();
	}
	const_iterator begin()const
	{
		return _ht.begin();
	}
	const_iterator end()const
	{
		return _ht.end();
	}
	std::pair<iterator, bool> insert(const K& key)
	{
		return _ht.insert(key);
	}
	bool erase(const K& key)
	{
		return _ht.Erase(key);
	}
	iterator find(const K& key)
	{
		return _ht.Find(key);
	}
private:
	HashTable<K, const K, KeyOfT, hash> _ht;
};

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

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

相关文章

题解 洛谷 Luogu P1182 数列分段 Section II 二分答案 C/C++

题目传送门&#xff1a; P1182 数列分段 Section II - 洛谷 | 计算机科学教育新生态https://www.luogu.com.cn/problem/P1182思路&#xff1a; 二分答案&#xff0c;每次以区间 [l, r] 中点 m 为每段和的阈值 判断在此前提下&#xff0c;划分段数是否不大于 M 是就记录答案…

Rust语言俄罗斯方块(漂亮的界面案例+详细的代码解说+完美运行)

tetris-demo A Tetris example written in Rust using Piston in under 500 lines of code 项目地址: https://gitcode.com/gh_mirrors/te/tetris-demo 项目介绍 "Tetris Example in Rust, v2" 是一个用Rust语言编写的俄罗斯方块游戏示例。这个项目不仅是一个简单…

Hot100 - 除自身以外数组的乘积

Hot100 - 除自身以外数组的乘积 最佳思路&#xff1a; 此问题的关键在于通过两次遍历&#xff0c;分别计算从左侧和右侧开始的累积乘积&#xff0c;以此避免使用额外的除法操作。 时间复杂度&#xff1a; 该算法的时间复杂度为 O(n)&#xff0c;因为我们只需要遍历数组两次。…

通过抓包,使用frida定位加密位置

首先我们抓取一下我们要测试的app的某一个目标api&#xff0c;通过抓api的包&#xff0c;得到关键字。 例如&#xff1a;关键字&#xff1a;x-sap-ri 我们得到想要的关键字后&#xff0c;通过拦截 类&#xff0c;寻找我们的关键字&#xff0c;及找到发包收包的位置&#xff0c…

【模型学习之路】TopK池化,全局池化

来学学图卷积中的池化操作 目录 DataBatch Dense Batching Dynamic Batching DataBatch 存取操作 TopKPooling GAP/GMP 一个例子 后话 DataBatch 当进行图级别的任务时&#xff0c;首先的任务是把多个图合成一个batch。 在Transformer中&#xff0c;一个句子的维度是…

<项目代码>YOLOv8 停车场空位识别<目标检测>

YOLOv8是一种单阶段&#xff08;one-stage&#xff09;检测算法&#xff0c;它将目标检测问题转化为一个回归问题&#xff0c;能够在一次前向传播过程中同时完成目标的分类和定位任务。相较于两阶段检测算法&#xff08;如Faster R-CNN&#xff09;&#xff0c;YOLOv8具有更高的…

如何在Python中进行数学建模?

数学建模是数据科学中使用的强大工具&#xff0c;通过数学方程和算法来表示真实世界的系统和现象。Python拥有丰富的库生态系统&#xff0c;为开发和实现数学模型提供了一个很好的平台。本文将指导您完成Python中的数学建模过程&#xff0c;重点关注数据科学中的应用。 数学建…

ThingsBoard规则链节点:GCP Pub/Sub 节点详解

目录 引言 1. GCP Pub/Sub 节点简介 2. 节点配置 2.1 基本配置示例 3. 使用场景 3.1 数据传输 3.2 数据分析 3.3 事件通知 3.4 任务调度 4. 实际项目中的应用 4.1 项目背景 4.2 项目需求 4.3 实现步骤 5. 总结 引言 ThingsBoard 是一个开源的物联网平台&#xff…

【工具变量】城市供应链创新试点数据(2007-2023年)

一、测算方式&#xff1a;参考C刊《经济管理》沈坤荣和乔刚老师&#xff08;2024&#xff09;的做法&#xff0c;使用“供应链创新与应用试点”的政策虚拟变量&#xff08;TreatPost&#xff09;表征。若样本城市为试点城市&#xff0c;则赋值为 1&#xff0c;否则为 0&#xf…

小程序租赁系统开发的优势与应用解析

内容概要 随着科技的迅猛发展&#xff0c;小程序租赁系统应运而生&#xff0c;成为许多企业优化业务的重要工具。首先&#xff0c;它提升了用户体验。想象一下&#xff0c;用户只需轻轻一点&#xff0c;就能够浏览和租赁心仪的商品&#xff0c;这种便捷的过程使繁琐的操作大大…

Spring MVC练习(前后端分离开发实例)

White graces&#xff1a;个人主页 &#x1f649;专栏推荐:Java入门知识&#x1f649; &#x1f439;今日诗词:二十五弦弹夜月&#xff0c;不胜清怨却飞来&#x1f439; ⛳️点赞 ☀️收藏⭐️关注&#x1f4ac;卑微小博主&#x1f64f; ⛳️点赞 ☀️收藏⭐️关注&#x1f4…

使用IDEA构建springboot项目+整合Mybatis

目录 目录 1.Springboot简介 2.SpringBoot的工作流程 3.SpringBoot框架的搭建和配置 4.用Springboot实现一个基本的select操作 5.SpringBoot项目部署非常简单&#xff0c;springBoot内嵌了 Tomcat、Jetty、Undertow 三种容器&#xff0c;其默认嵌入的容器是 Tomcat&#xff0c;…

不玩PS抠图了,改玩Python抠图

网上找了两个苏轼的印章图片&#xff1a; 把这两个印章抠出来的话&#xff0c;对于不少PS高手来说是相当容易&#xff0c;但是要去掉其中的水印&#xff0c;可能要用仿制图章慢慢描绘&#xff0c;图章的边缘也要慢慢勾画或者用通道抠图之类来处理&#xff0c;而且印章的红色也不…

ElasticSearch的下载和基本使用(通过apifox)

1.概述 一个开源的高扩展的分布式全文检索引擎&#xff0c;近乎实时的存储&#xff0c;检索数据 2.安装路径 Elasticsearch 7.8.0 | Elastic 安装后启动elasticsearch-7.8.0\bin里的elasticsearch.bat文件&#xff0c; 启动后就可以访问本地的es库http://localhost:9200/ …

26届JAVA 学习日记——Day16

2024.11.27 周三 尽量在抽出时间做项目&#xff0c;持续学习优化简历&#xff0c;等到基础的八股都熟悉、leetcode热题100刷完、苍穹外卖项目AI项目彻底完成投简历&#xff0c;目标是找到日常实习&#xff0c;然后边做边准备暑期实习。 八股 WebSocket WebSocket是什么&…

Javaweb 前端 HTML css 案例 总结

顶部导航栏 弹性布局 搜索表单区域 表单标签 表单标签&#xff0c;表单项 复选&#xff0c;一次选多个 隐藏域&#xff0c;看不到&#xff0c;但会传参数 text输入框 radio单选 男女&#xff0c;是 前端页面上显示的值 搜索表单区域 button 按钮 表格数据展示区域 fo…

每日一练:【动态规划算法】斐波那契数列模型之使用最小花费爬楼梯(easy)

1. 题目链接&#xff1a;746. 使用最小花费爬楼梯 2. 题目描述 根据一般的思维&#xff0c;我们会认为本题中数组的最后一个位置是楼顶&#xff0c;但是根据第一个例子&#xff0c;如果最后一个位置是楼顶&#xff0c;花费最少应该为10&#xff0c;但是结果是15&#xff0c;因…

HCIP——堆叠技术实验配置

目录 一、堆叠的理论知识 二、堆叠技术实验配置 三、总结 一、堆叠的理论知识 1.1堆叠概述&#xff1a; 是指将两台交换机通过堆叠线缆连接在一起&#xff0c;从逻辑上变成一台交换设备&#xff0c;作为一个整体参与数据的转发。 1.2堆叠的基本概念 堆叠系统中所有的单台…

微软正在测试 Windows 11 对第三方密钥的支持

微软目前正在测试 WebAuthn API 更新&#xff0c;该更新增加了对使用第三方密钥提供商进行 Windows 11 无密码身份验证的支持。 密钥使用生物特征认证&#xff0c;例如指纹和面部识别&#xff0c;提供比传统密码更安全、更方便的替代方案&#xff0c;从而显著降低数据泄露风险…

ubuntu 安装proxychains

在Ubuntu上安装Proxychains&#xff0c;你可以按照以下步骤操作&#xff1a; 1、更新列表 sudo apt-update 2、安装Proxychains sudo apt-get install proxychains 3、安装完成后&#xff0c;你可以通过编辑/etc/proxychains.conf文件来配置代理规则 以下是一个简单的配置示例&…