unordered_map与unordered_set的实现

news2024/11/28 12:41:08

目录

1.底层结构

1)方式

2)哈希冲突

3)哈希函数

4)哈希冲突的解决

1.闭散列

1)线性探测

扩容:

2)二次探测

2.开散列

1)概念

2)实现

插入操作:

删除操作:

查找操作:

哈希桶的销毁:

3)迭代器的实现

4)begin -- end

2.unordered_set的实现

3.unordered_map的实现


1.底层结构

两种容器的底层结构均为哈希表

概念:通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立

一一映射的关系,那么在查找时通过该函数可以很快找到该元素

1)方式

        插入: 根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放

        搜索:对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功

2)哈希冲突

在上述的操作中,难免会出现不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。

而哈希冲突的发生是不可避免的

3)哈希函数

引起哈希冲突的一个原因可能是:哈希函数设计不够合理

哈希函数设计原则
1.哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有 m 个地址时,其值域必须在0 m-1 之间
2.哈希函数计算出来的地址能均匀分布在整个空间中
3.哈希函数应该比较简单
常见的哈希哈数:
直接定址法、除留余数法、平方取中法、折叠法、随机数法、数学分析法

4)哈希冲突的解决

解决哈希冲突 两种常见的方法是: 闭散列 开散列
1.闭散列
闭散列:也叫 开放定址法 ,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有
空位置,那么可以把key存放到冲突位置中的 “下一个” 空位置中去。
1)线性探测
从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止
例如下面的元素44,在位置冲突的情况下,就需要去找到下一个空位

比如上述,如果删除了6号元素,直接标记空的话:
查询44号元素的话,就会查找失败
扩容:

扩容有个前提条件,即载荷因子超出预定值时进行扩容

散列表中载荷因子的定义为:α = 填入表中的元素个数 / 散列表的长度

α 是散列表装满程度的标志因子。由于表长是定值,α 与“填入表中的元素个数”成正比,所以,α 越大,表明填入表中的元素越多,产生冲突的可能性就越大:反之,越小,标明填入表中的元素越少,产生冲突的可能性就越小。实际上,散列表的平均查找长度是载荷因子 α 的函数,只是不同处理冲突的方法有不同的函数。

对于开放定址法,载荷因子一般 严格控制在0.7 - 0.8以下。超过0.8的话,查表的效率会大大降低
2)二次探测

为了避免线性探测的逐个寻找空位,即有了二次探测:

若冲突,则:

(x + i ^ 1)/ m、(x + i ^ 2)、......i为1,2,3,4,5.....

2.开散列
1)概念
开散列法又叫链地址法 ( 开链法 ) ,首先对关键码集合用散列函数计算散列地址,具有相同地
址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链
接起来,各链表的头结点存储在哈希表中
2)实现
    template<class T>
	struct HashNode
	{
		HashNode<T>* _next;
		T _data;

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

    template<class K, class T, class KeyOfT, class Hash = HashFunc<K>>
	class HashTable
	{
		typedef HashNode<T> Node;
    public:
		HashTable()
		{
			_tables.resize(10, nullptr);
		}

	private:
		vector<Node*> _tables;
		size_t _n = 0;
	};

底层即为vector,vector存储的每个数据为节点的指针

HashTable的构造函数即为重要,因为要事先开辟一些空间,防止访问空指针导致错误

插入操作:

未达到载荷因子就无须扩容,直接头插即可,若达到,则扩容后插入即可

		pair<Iterator, bool> Insert(const T& data)
		{
			Hash hs;
			KeyOfT kot;
			Node* cur = nullptr;

			Iterator it = Find(kot(data));
			if (it != End())
				return make_pair(it, false);

			if (_n == _tables.size())
			{
				// 扩容
                // ......
			}
			else // 正常找空位插入即可
			{
				size_t hashi = hs(kot(data)) % _tables.size();
				if (_tables[hashi] == nullptr)
				{
					_tables[hashi] = new Node(data);
					cur = _tables[hashi]; // 这里cur一开始忘了给值,小修bug
				}
				else
				{
					cur = new Node(data);
					Node* tmp = _tables[hashi];

					cur->_next = tmp;
					_tables[hashi] = cur;
				}
				_n++;
			}
			return make_pair(Iterator(cur, this), true);
		}

返回值用pair<Iterator, bool> 是因为方便unordered_map的 [] 的实现

扩容操作:
创建一个新表,然后遍历旧表,将旧表的值一个个按照新表的规则插入新表中,最后将需要插入的值正常插入即可

				// 扩容
				size_t newsize = _tables.size() * 2;
				vector<Node*> newtables(newsize, nullptr);

				for (int i = 0; i < _tables.size(); ++i)
				{
					if (_tables[i])
					{
						cur = _tables[i];
						while (cur)
						{
							Node* next = cur->_next; // 先保存下一个位置

							size_t hashi = hs(kot(cur->_data)) % newsize;
							if (newtables[hashi] == nullptr)
							{
								cur->_next = nullptr; // cur的next应该及时处理,不然容易出现死循环
								newtables[hashi] = cur;
							}
							else
							{
								// 头插
								cur->_next = newtables[hashi];
								newtables[hashi] = cur;
							}
							cur = next;
						}
						_tables[i] = nullptr;
					}
				}
				_tables.swap(newtables);
				Insert(data);

注意,每当拿出来一个节点时,要将其_next置空或者赋值,否则会保留原表中的链接逻辑,导致后续错乱

删除操作:

删除相比插入起来就简单多了

只需要找到相应的节点,然后复原链接关系,最后delete即可:

两种情况:

1.删除头部

直接将_tables[hashi] 赋值给next然后delete掉头即可

2.删除非头部

直接prev->next = cur->next然后delete掉cur即可

	    bool Erase(const K& key)
		{
			Hash hs;
			size_t hashi = hs(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;
				}
				else
				{
					prev = cur;
					cur = cur->_next;
				}
			}
			return false;
		}
查找操作:

找到头节点后遍历即可:

		Iterator Find(const K& key)
		{
			Hash hs;
			KeyOfT kot;

			size_t hashi = hs(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return Iterator(cur, this);
				}
				cur = cur->_next;
			}
			return Iterator(nullptr, this);
		}
哈希桶的销毁:
		// 哈希桶的销毁
		~HashTable()
		{
			for (int i = 0; i < _tables.size(); ++i)
			{
				if (_tables[i])
				{
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;

						delete cur;
						cur = next;
					}
				}
				_tables[i] = nullptr;
			}
		}
3)迭代器的实现

为了实现++,我们需要获取到_tables(属于哈希的底层结构),因此必须设置友元或者实现内部类

	template<class K, class T, class KeyOfT, class Hash, class Ref, class Ptr>
	struct __HTIterator
	{
		typedef __HTIterator<K, T, KeyOfT, Hash, Ref, Ptr> Self;
		typedef HashNode<T> Node;
		Node* _node;
		HashTable<K, T, KeyOfT, Hash>* _pht;
		KeyOfT kot;
		Hash hs;

		__HTIterator(Node* node, HashTable<K, T, KeyOfT, Hash>* pht)
			:_node(node)
			, _pht(pht)
		{}

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

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

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

构造时,本来只需要node节点的,但是由于需要_tables的原因,需要拿到一个HashTable的指针方便访问。

这里的迭代器中,难度稍微高点的依旧是++操作:

1.若该桶未走完,则next即可

2.若该桶走完,则寻找下一个头不为空的点返回其头部即可

3.若所有桶走完,返回nullptr即可

		Self& operator++()
		{
			if (_node->_next)
			{
				// 1.当前桶没走完,找当前桶的下一个节点
				_node = _node->_next;
			}
			else
			{
				// 2.当前桶走完了,找下一个不为空的桶的第一个节点
				KeyOfT kot;
				Hash hs;
				size_t i = hs(kot(_node->_data)) % _pht->_tables.size();
				++i;
				for (; i < _pht->_tables.size(); i++)
				{
					if (_pht->_tables[i])
						break;
				}

				if (i == _pht->_tables.size())
				{
					// 3.所有桶都走完了,最后一个的下一个用nullptr标记
					_node = nullptr;
				}
				else
				{
					_node = _pht->_tables[i];
				}
			}

			return *this;
		}

逻辑理清楚了,实现起来不会太难

4)begin -- end
template<class K, class T, class KeyOfT, class Hash = HashFunc<K>>
	class HashTable
	{
		typedef HashNode<T> Node;
		// 友元声明
		template<class K, class T, class KeyOfT, class Hash, class Ref, class Ptr>
		friend struct __HTIterator;

	public:
		typedef __HTIterator<K, T, KeyOfT, Hash, T&, T*> Iterator;
		typedef __HTIterator<K, const T, KeyOfT, Hash, const T&, const T*> Const_Iterator;

		Iterator Begin()
		{
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				if (cur)
				{
					return Iterator(cur, this);
				}
			}
			return End();
		}

		Iterator End()
		{
			return Iterator(nullptr, this);
		}

		Const_Iterator Begin() const
		{
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				if (cur)
				{
					return Const_Iterator(cur, this);
				}
			}
			return End();
		}

		Const_Iterator End() const
		{
			return Const_Iterator(nullptr, this);
		}
    };

2.unordered_set的实现

template<class K>
class myunordered_set
{
	struct SetKeyOfT
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
public:
	typedef typename Hash_bucket::HashTable<K, const K, SetKeyOfT>::Iterator iterator;
	typedef typename Hash_bucket::HashTable<K, const K, SetKeyOfT>::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();
	}

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

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

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

3.unordered_map的实现


template<class K, class V>
class myunordered_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>::Iterator iterator;
	typedef typename Hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT>::Const_Iterator const_iterator;

public:
	iterator begin()
	{
		return _ht.Begin();
	}

	iterator end()
	{
		return _ht.End();
	}

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

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

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

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

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

	V& operator[](const K& key)
	{
		pair<iterator, bool> ret = insert(make_pair(key, V()));
		return (*(ret.first)).second;
	}

private:
	Hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT> _ht;
};

具体代码参考 ---- Hash

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

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

相关文章

8个不可错过的高清视频素材网

寻找优质高清视频素材是许多创意工作者和内容创作者必不可少的一项任务。无论是在制作广告、影视作品&#xff0c;还是在进行视频编辑和设计工作&#xff0c;高质量的视频素材都能为你的作品增色不少。 推荐8个备受好评的高清视频素材网站&#xff0c;这些网站提供丰富多样、高…

Django基础知识

文章目录 新建Django项目helloworld关联数据库admin 新建Django项目 创建django-admin startproject project_name 运行 python manage.py runserver 创建app: python manage.py startapp app_name 目录&#xff1a; 配置文件 settings.py 路由配置 urls.py 项目管理 manage.p…

facebook广告效果下降,可能是进入了疲劳期

在做facebook广告的时候&#xff0c;你是否遇到过原本效果很好的广告&#xff0c;突然开始走下坡路的情况&#xff0c;这可能是因为广告进入了疲劳期&#xff0c;广告疲劳期指的是广告展现频率过高&#xff0c;用户多次看到相同广告后就对此感到了厌倦&#xff0c;所以会出现广…

IMA自动接触系统ACS操作手测

IMA自动接触系统ACS操作手测

飞轮科技首席执行官马如悦受邀出席可信数据库发展大会,三大事件,一文解读!

近日&#xff0c;由中国通信标准化协会、大数据技术标准推进委员会主办&#xff0c;InfoQ 极客传媒联合主办的 “2024 可信数据库发展大会” 在京成功召开。此次大会汇聚了众多数据库行业领军企业、专家学者&#xff0c;共同探讨全球数据库发展趋势&#xff0c;分享最具权威性的…

合成数据的pipline

参考&#xff1a;https://github.com/yizhongw/self-instruct 总体来说就是 大模型自己遵循一套流程来生成数据&#xff0c;然后来微调自己。 1.指令生成 每一个迭代都选8个任务的指令作为该任务的提示样本&#xff0c;其中6个是人写的&#xff0c;2个是生成的。 然后组成输…

从零到一:家政保洁小程序搭建全攻略与功能作用深度解析

目录 一、家政保洁小程序主要功能 二、家政保洁小程序搭建教程 &#xff08;一&#xff09;前期准备 &#xff08;二&#xff09;注册与选择工具 &#xff08;三&#xff09;设计与开发 &#xff08;四&#xff09;测试与优化 &#xff08;五&#xff09;发布与推广 一、…

笔记本CPU天梯图(2024年8月),含AMD/骁龙等新CPU

原文地址&#xff08;高清无水印原图/持续更新/含榜单出处链接&#xff09;&#xff1a; 2024年8月笔记本CPU天梯图 2024年8月笔记本CPU天梯图 2024年8月5日更新日志&#xff1a;常规更新Cinebench R23、PassMark笔记本CPU天梯图&#xff0c;新增Geekbench 6.2单核多核天梯图&…

您真的了解人类反馈强化学习(RLHF)吗?

生成性人工智能&#xff0c;就像ChatGPT和Gemini这样的应用&#xff0c;现在可火了&#xff0c;感觉我们生活中越来越离不开它们。 不过呢&#xff0c;这些工具虽然厉害&#xff0c;但用的时候也得留个心眼&#xff0c;因为它们可能会搞出些问题来。比如&#xff0c;有时候AI可…

DP转Type-c方案 带PD快充(外接显卡与VR)

DP转Type-C技术允许用户将DisplayPort信号转换为Type-C接口&#xff0c;‌以便连接和支持Type-C接口的设备。‌ DP转Type-C技术主要应用于需要将DisplayPort信号转换为Type-C接口的情况&#xff0c;‌以便连接和支持只有Type-C接口的设备。‌这种转换技术通过专门的转换器或连…

前端项目中的Server-sent Events(SSE)项目实践及其与websocket的区别

前端项目中的Server-sent Events(SSE)项目实践 前言 在前端开发中&#xff0c;实时数据更新是提升用户体验的重要因素之一。Server-SentEvents(SSE)是一种高效的技术&#xff0c;允许服务器通过单向连接将实时数据推送到客户端。下面将从SSE的基本改变&#xff0c;使用场景展…

TCP问题总结

TCP三次握手与四次挥手 1.TCP 头格式有哪些&#xff1f; 标注颜⾊的表示与本⽂关联⽐较⼤的字段&#xff0c;其他字段不做详细阐述。 序列号&#xff1a;在建⽴连接时由计算机⽣成的随机数作为其初始值&#xff0c;通过 SYN 包传给接收端主机&#xff0c;每发送⼀次数 据&am…

MapReduce入门教程

这可不是目录 入门定义与说明数据分析Map和Reduce阶段的任务<Kn,Vn>分析MapReduce的数据类型其他说明(持续更新) 开发案例(持续更新)自定义的wordcountcsv文件操作序列化操作 入门 定义与说明 数据分析 以下未数据分析示意图 Map和Reduce阶段的任务 Map阶段的任务&a…

安科瑞Acrel-2000ES储能能量管理系统在新型电力系统下分布式储能的研究

摘要&#xff1a;传统电力系统的结构和运行模式在以新能源为主体的新型电力系统中发生了巨大的变化&#xff0c;分布式储能作为电力系统中重要的能量调节器&#xff0c;也迎来了新的发展机遇。立足于储能技术发展现状&#xff0c;分析了分布式储能技术特点及在清洁可再生能源方…

ALLEGRO直接转PADS方法

1.ALLEGRO转PADS之前系统上添加用户变量 打开环境变量窗口&#xff0c;以WIN10为例 添加变量和值 变量名&#xff1a;AEX_BIN_ROOT值&#xff1a; PADS软件中translators软件的bin目录路径比如我的&#xff1a;AEX_BIN_ROOTD:\MentorGraphics\PADSVX.2.3\SDD_HOME\translator…

用python的manim库实现表格的绘制和制作【table 上】

表格的定义&#xff1a;按行和列排列的信息&#xff08;如数字和说明&#xff09;。 这是人们参加的运动的表格&#xff1a; 接下来绘制一些表格&#xff1a; 1. 创建一个包含小数数字的表格{DecimalTable} DecimalTable 是 Manim 中用于创建一个包含小数数字的表格的类。这个…

SD-WAN在海外网络加速中的优势

随着全球化的加剧&#xff0c;企业在海外业务拓展中面临着网络延迟、数据安全和成本等一系列挑战。针对这些问题&#xff0c;SD-WAN(软件定义广域网&#xff09;成为了企业网络架构的良好选择。本文将详细介绍SD-WAN在海外加速中的应用和优势。 1.SD-WAN是什么? SD-WAN是一种网…

软件开发者申请代码签名证书流程

软件目前已经成为人们生活和工作不可或缺的一部分&#xff0c;随着互联网的普及和软件分发渠道的多样化&#xff0c;软件的安全性和可信度变得越来越重要。 为了确保软件的完整性、安全性和来源可信性&#xff0c;代码签名证书应运而生。 在软件完成了开发之后&#xff0c;开…

硬件面试经典 100 题(31~40 题)

31、多级放大电路的级间耦合方式有哪几种&#xff1f;哪种耦合方式的电路零点偏移最严重&#xff1f;哪种耦合方式可以实现阻抗变换&#xff1f; 有三种耦合方式&#xff1a;直接耦合、阻容耦合、变压器耦合。直接耦合的电路零点漂移最严重&#xff0c;变压器耦合的电路可以实现…

软件测试经典面试题(答案解析+文档)

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 1、B/S架构和C/S架构区别 B/S 只需要有操作系统和浏览器就行&#xff0c;可以实现跨平台&#xff0c;客户端零维护&#xff0c;维护成本低&#xff0c;但是个性化…