c++ - unordered_set与unordered_map模拟实现

news2024/12/28 21:24:36

文章目录

  • 前言
    • 一、unordered_set模拟实现
    • 二、unordered_map模拟实现


前言

1、unordered_setunordered_map的介绍与接口使用可参考:unordered_set 、 unordered_map。
2、unordered_setunordered_map 的底层实现都是基于哈希表的。哈希表是一种通过哈希函数组织数据,以支持快速插入和搜索的数据结构。在 C++ STL(Standard Template Library)中,这两种容器利用哈希表的高效特性来提供快速的查找、插入和删除操作。
3、哈希表

namespace OpenHash
{
	//前置声明 -->给迭代器向上查找
	template<class K, class T, class KeyOfValue, class HF >
	class HashBucket;

	//哈希节点
	template<class T>
	struct HashBucketNode
	{
		HashBucketNode(const T& val)
			: _pNext(nullptr), _val(val)
		{}
		HashBucketNode<T>* _pNext;	
		T _val;	//数据
	};

	//迭代器
	template<class K,class T,class  KeyOfValue,class HF,class Ptr,class Ref>
	struct HsIterator
	{
		typedef HashBucketNode<T> Node;
		typedef HashBucket<K, T, KeyOfValue, HF> Hash;
		typedef HsIterator<K, T, KeyOfValue, HF,Ptr,Ref> Self;

		Node* _cur;
		const Hash* _hash;
		HsIterator(const  Hash* hash,  Node* cur ):_cur(cur),_hash(hash){}

		Self& operator++()
		{	
			KeyOfValue func;
			HF hf;
			if (_cur->_pNext != nullptr)
			{
				_cur = _cur->_pNext;
			}
			else
			{
				int hashi = hf(func(_cur->_val)) % _hash->_table.size();
				Node* cur = _hash->_table[++hashi];
				while (hashi < _hash->_table.size() && cur == nullptr)
				{
					cur = _hash->_table[hashi];
					hashi++;	
				}

				_cur = cur;
			}
			return *this;
		}

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

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

		Ref operator*()
		{
			return _cur->_val;
		}

		Ptr operator->()
		{
			return &(_cur->_val);
		}
	}; 

	template<class K,class T, class KeyOfValue, class HF >
	class HashBucket
	{
		typedef HashBucketNode<T> Node;	//节点重命名
		typedef HashBucket<K,T, KeyOfValue, HF> Self;	//自身重命名
	public:
		typedef  HsIterator<K, T, KeyOfValue, HF, T*, T&> Iterator;	//迭代器重命名
		typedef HsIterator<K, T, KeyOfValue, HF,const T*,const T&> ConstIterator;	//const迭代器重命名

		//迭代器作为有友元
		template<class K, class T, class  KeyOfValue, class HF, class Ptr, class Ref>
		friend struct HsIterator;

		//构造函数 哈希桶初始化大小为10
		HashBucket(size_t capacity = 10)
			: _table(10)
			, _size(0)
		{}

		//拷贝构造
		HashBucket(const Self& self)
		{
			_table.resize(10);
			//通过遍历Self和插入接口即可
			for (int i = 0; i < self._table.size(); i++)
			{
				Node* cur = self._table[i];
				while (cur)
				{
					Insert(cur->_val);
					cur = cur->_pNext;
				}
			}
		}

		//赋值重载
		Self& operator=(Self self)
		{
			Clear();
			Swap(self);
			return *this;
		}

		~HashBucket()
		{
			Clear();
		}

		Iterator Begin()
		{
			//找到第一个不为空的桶
			int i = 0;
			while (_table[i] == nullptr)
			{
				i++;
			}

			return Iterator(this, _table[i]);
		}

		Iterator End()
		{
			//用nullptr代表最后一个元素的后一个位置
			return Iterator(this, nullptr);
		}

		ConstIterator Begin() const
		{
			int i = 0;
			while (_table[i] == nullptr)
			{
				i++;
			}

			return ConstIterator(this, _table[i]);
		}

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

		//返回值:为了unordered_map的重载[]能够使用
		pair<bool, Iterator>Insert(const T & val)
		{
			//将数据转化为键值
			KeyOfValue func;

			//不允许重复键值
			Iterator it = Find(func(val));
			if ( it != End())
			{
				return make_pair(false,it);
			}

			//将键值转化为整形(哈希地址)
			HF ik;
			int hashi = ik(func(val)) % _table.size();

			//超过负载因子
			if (_size == _table.size())
			{
				//库容2倍
				vector<Node*> tmp(_table.size() * 2);

				//将原来的数据转移到tmp
				for (int i = 0; i < _table.size(); i++)
				{
					if (_table[i] != nullptr)
					{
						Node* cur = _table[i];
						while (cur)
						{
							//重新获取哈希地址
							int k = ik(func(cur->_val)) % tmp.size();
							//头插入tmp
							Node* next = cur->_pNext;
							cur->_pNext = tmp[k];
							tmp[k] = cur;
							cur = next;
						}
					}
				}
				//将容器进行交换
				_table.swap(tmp);
			}

			//插入
			Node* cur = new Node(val);
			cur->_pNext = _table[hashi];
			_table[hashi] = cur;
			_size++;

			return make_pair(true, Iterator(this,cur));
		}

		// 删除哈希桶中为data的元素(data不会重复)
		bool Erase(const K& key)
		{
			//将数据转化为键值
			KeyOfValue func;

			//将数据转化为键值
			HF ik;
			int hashi = ik(key) % _table.size();

			//获取头节点
			Node* cur = _table[hashi];
			//前驱指针
			Node* prev = nullptr;
			while (cur)
			{
				//找到,重新连接
				if (func(cur->_val) == key)
				{
					if (prev == nullptr)
					{
						_table[hashi] = cur->_pNext;
					}
					else
					{
						prev->_pNext = cur->_pNext;
					}
					_size--;
					delete cur;
					return true;
				}
				prev = cur;
				cur = cur->_pNext;
			}
			
			return false;
		}

		Iterator Find(const K& key)
		{
			//将数据转化为键值
			KeyOfValue func;

			//将数据转化为键值
			HF ik;
			int hashi = ik(key) % _table.size();

			//获取头节点
			Node* cur = _table[hashi];

			//查找
			while (cur)
			{
				if (func(cur->_val) == key)
				{
					return Iterator(this,cur);
				}
				cur = cur->_pNext;
			}

			return Iterator(this, nullptr);
		}

		//大小
		size_t Size()const
		{
			return _size;
		}

		//是否为空
		bool Empty()const
		{
			return 0 == _size;
		}

		//清空
		void Clear()
		{
			//遍历容器清空每个节点
			for (int i = 0; i < _table.size(); i++)
			{
				Node* cur = _table[i];
				while (cur)
				{
					Node* next = cur->_pNext;
					delete cur;
					cur = next;
				}
				//最后置空
				_table[i] = nullptr;
			}
			//大小为0
			_size = 0;
		}

		//交换
		void Swap(Self& ht)
		{
			//交换内部容器即可
			_table.swap(ht._table);
			swap(_size, ht._size);
		}

	private:
		vector<Node*> _table;	//储存哈希桶头节点指针
		size_t _size = 0;      // 哈希表中有效元素的个数
	};
};

一、unordered_set模拟实现

1、底层实现

unordered_set 的底层是一个哈希表。但是,它只存储元素本身,而不是键值对。因此,每个元素的值同时也是其唯一的键。

2、特点

无序性:元素的顺序不保证,且可能会随着插入和删除操作而改变。
元素的唯一性:unordered_set 中的每个元素都是唯一的。
快速查找:同样地,平均情况下,查找、插入和删除操作的时间复杂度都是 O(1)。

3、代码实现
(1)基础框架

//哈希函数
template<class	K>
class HashFunc
{
public:
	size_t operator()(const K& val)
	{
		return val;
	}
};
//特化版本的哈希函数
template<>
class HashFunc<string>
{
public:
	size_t operator()(const string& s)
	{
		const char* str = s.c_str();
		unsigned int seed = 131; // 31 131 1313 13131 131313
		unsigned int hash = 0;
		while (*str)
		{
			hash = hash * seed + (*str++);
		}

		return hash;
	}
};

//数据转化为键值的函数
template<class K>
struct KeyOfValue
{
	const K& operator()(const K& data)
	{
		return data;
	}
};

//unordered_set
template<class K, class HF = HashFunc<K>>
class unordered_set
{
	//哈希表重命名
	typedef OpenHash::HashBucket<K, const K, KeyOfValue<K>, HF> HT;
public:
	//对哈希表迭代器进行重命名
	typename typedef  HT::Iterator iterator;
	typename typedef  HT::ConstIterator const_iterator;

private:
	//哈希表
	HT _ht;
};

unordered_set模板参数
K:即充当键值也充当数据
HF:哈希函数,用于计算哈希值

unordered_set成员变量
_ht: 哈希表

给哈希表传模板参数
K : 代表键值类型,该类型变量用于查找删除等接口
const K:代表数据类型,因为不能被修改所以加上const修饰,该类型变量用于插入等
KeyOfValue<K>:将数据转化为键值(在unordered_set中不明显,因为键值类型与数据类型一致,主要在unordered_map中体现)
HF:哈希函数,用于计算哈希值

(2)接口实现
这里的接口都是直接复用哈希表的接口,包括插入、删除、查找等,析构不用我们自己实现,因为结束时析构自动调用哈希表的析构函数进行清理。

//构造函数
	unordered_set() : _ht()
	{}

	//拷贝构造函数
	unordered_set(const unordered_set& p) :_ht(p._ht)
	{}

	//赋值函数
	unordered_set& operator=(const unordered_set& p)
	{
		_ht = p._ht;

		return *this;
	}

	//迭代器
	iterator begin()
	{
		return _ht.Begin();
	}
	iterator end()
	{
		return _ht.End();
	}
	const_iterator begin() const
	{
		return _ht.Begin();
	}
	const_iterator end()const
	{
		return _ht.End();
	}

	//元素个数
	size_t size()const { return _ht.Size(); }

	//是否为空
	bool empty()const { return _ht.Empty(); }

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

	//插入
	pair<bool, iterator> insert(const K& valye)
	{
		return _ht.Insert(valye);
	}

	//删除
	bool  erase(const K& position)
	{
		return _ht.Erase(position);
	}

(3)测试

void test_set()
{
	//默认构造
	unordered_set<int> s;
	//插入
	s.insert(1);
	s.insert(2);
	s.insert(3);
	s.insert(4);
	
	cout << "s1:";
	//拷贝构造
	unordered_set<int> s1(s);
	//迭代器遍历
	unordered_set<int>::iterator it1 = s1.begin();
	while (it1 != s1.end())
	{
		cout << *it1 << " ";
		++it1;
	}

	cout << endl;
	cout << "s2:";
	//赋值
	unordered_set<int> s2;
	s2 = s;
	//删除
	s2.erase(1);
	s2.erase(2);
	//迭代器遍历
	unordered_set<int>::iterator it2 = s2.begin();
	while (it2 != s2.end())
	{
		cout << *it2 << " ";
		++it2;
	}
}

在这里插入图片描述

二、unordered_map模拟实现

1、底层实现

unordered_map 的底层是一个哈希表,每个元素都是一个键值对(key-value pair)。通过哈希函数,键(key)被映射到一个特定的位置(也称为桶或槽位),这个位置存储了相应的值(value)。如果多个键经过哈希函数计算后得到相同的位置,就发生了哈希冲突,此时通常通过链表或红黑树等数据结构来解决冲突。

2、特点

无序性:元素的顺序不保证,且可能会随着插入和删除操作而改变。
快速查找:平均情况下,查找、插入和删除操作的时间复杂度都是 O(1)。
键的唯一性:每个键在 unordered_map 中必须是唯一的。

3、代码实现
(1)基础框架

//哈希函数
template<class	K>
class HashFunc
{
public:
	size_t operator()(const K& val)
	{
		return val;
	}
};
//特化string
template<>
class HashFunc<string>
{
public:
	size_t operator()(const string& s)
	{
		const char* str = s.c_str();
		unsigned int seed = 131; 
		unsigned int hash = 0;
		while (*str)
		{
			hash = hash * seed + (*str++);
		}

		return hash;
	}
};

//数据转化为键值
template<class K, class V>
struct KeyOfValue
{
	const K& operator()(const pair<K, V>& data)
	{
		return data.first;
	}
};

template<class K, class V, class HF = HashFunc<K>>
class unordered_map
{
	//对哈希表重命名
	typedef OpenHash::HashBucket<K, pair<const K, V>, KeyOfValue<K, V>, HF>  HT;
	
public:
	//迭代器重命名
	typename typedef  HT::Iterator iterator;
	typename typedef  HT::ConstIterator const_iterator;

private:
	//哈希表
	HT _ht;
};

unordered_map模板参数
K:键值类型
V:数据类型
HF:哈希函数

unordered_map成员变量
_ht:哈希表

给哈希表传模板参数
K:键值,用于删除、查找等接口
KeyOfValue<K, V>:将数据转化为键值
pair<const K, V>:作为数据,其中const K作为键值类型(因为键值不能改需要通过const修饰),V作为映射的数据类型,通过KeyOfValue<K, V> 函数返回键值(如在插入时只传入pair<const K, V>类型数据,无法通过数据进行比较,所以需要通过该函数转化)。
HF:哈希函数

(2)接口实现
A . 重载[ ] : 该接口的特点是如果该键值的不存在,则进行插入(键值映射的数据用默认构造初始化),如果存在就返回键值映射的数据,所以需要与插入接口的配合,通过插入接口的返回值来配合。

//重载[]
V& operator[](const K& key)
{
	pair < bool, iterator > ret = _ht.Insert(make_pair(key, V()));
	return ret.second->second;
}

B . 其他接口都是直接复用哈希表的接口,包括插入、删除、查找等,析构不用我们自己实现,因为结束时析构自动调用哈希表的析构函数进行清理。

//构造函数
unordered_map() : _ht()
{}

//拷贝构造
unordered_map(const unordered_map& p) :_ht(p._ht)
{}

//赋值
unordered_map & operator=(const unordered_map& p)
{
	_ht = p._ht;

	return *this;
}

//迭代器
iterator begin()
{
	return _ht.Begin();
}
iterator end()
{
	return _ht.End();
}
const_iterator begin() const
{
	return _ht.Begin();
}
const_iterator end()const
{
	return _ht.End();
}

//元素个数
size_t size()const
{ 
	return _ht.Size();
}
//是否为空
bool empty()const 
{ 
	return _ht.Empty();
}

//查找
iterator find(const K& key) 
{ 
	return _ht.Find(key);
}
//
 插入
pair<bool,iterator> insert(const pair<K, V>& valye)
{
	return _ht.Insert(valye);
}

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

(3)测试

 void test_map()
{
	//默认构造
	unordered_map<int, int> m;
	//插入
	m.insert({ 1,1 });
	m.insert({ 2,2 });
	m.insert({ 3,3 });
	m.insert({ 4,4 });

	cout << "m1:";
	//拷贝构造
	unordered_map<int, int> m1 = m;
	//迭代器遍历
	unordered_map<int, int>::iterator it1 = m1.begin();
	while (it1 != m1.end())
	{
		cout << it1->first << ":" << it1->second << "   ";
		++it1;
	}
	cout << endl;
	cout << "m2:";
	//赋值
	unordered_map<int, int> m2;
	m2 = m;
	//删除
	m2.erase(1);
	m2.erase(2);
	//重载[]
	m2[3] = 100;
	m2[5] = 100;
	//迭代器遍历
	unordered_map<int, int>::iterator it2 = m2.begin();
	while (it2 != m2.end())
	{
		cout << it2->first << ":" << it2->second << "   ";
		++it2;
	}
}

在这里插入图片描述

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

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

相关文章

HarmonyOS(48) 挂载卸载事件 UI组件的添加和删除监听

UI组件的添加和删除监听 一级目录示例代码参考资料 一级目录 我们通过if条件添加组件的时候&#xff0c;是可以通过onAttach、onDetach、onAppear、onDisAppear来监听组件的添加和删除。 示例代码 // xxx.ets// xxx.ets import { promptAction } from kit.ArkUIEntry Compo…

2024华数杯数学建模A题完整论文讲解(含每一问python代码+结果+可视化图)

大家好呀&#xff0c;从发布赛题一直到现在&#xff0c;总算完成了2024 年华数杯全国大学生数学建模竞赛A题机器臂关节角路径的优化设计完整的成品论文。 本论文可以保证原创&#xff0c;保证高质量。绝不是随便引用一大堆模型和代码复制粘贴进来完全没有应用糊弄人的垃圾半成…

VBA信息获取与处理:VBA代码分类及如何利用代码自动关闭空闲文件

《VBA信息获取与处理》教程(版权10178984)是我推出第六套教程&#xff0c;目前已经是第一版修订了。这套教程定位于最高级&#xff0c;是学完初级&#xff0c;中级后的教程。这部教程给大家讲解的内容有&#xff1a;跨应用程序信息获得、随机信息的利用、电子邮件的发送、VBA互…

LC65---2164.对奇偶下标分别排序(排序)--Java版

1.题目 2.思路 &#xff08;1&#xff09;分别提取奇数下标和偶数下标的元素。 &#xff08;2&#xff09;对奇数下标的元素按非递增顺序排序&#xff0c;对偶数下标的元素按非递减顺序排序。 (3)最后将排列好的数字进行合并。 补充&#xff1a; 3.代码实现 class Solution…

PyCharm 2024.1 总结和最新变化

​ 您好&#xff0c;我是程序员小羊&#xff01; 前言 PyCharm 2024.1 是 JetBrains 最新发布的Python集成开发环境&#xff08;IDE&#xff09;&#xff0c;旨在提供更强大的功能和更好的用户体验。以下是对这个版本的总结和最新变化的介绍 智能代码建议和自动完成&#xff1a…

C语言 ——— 学习并使用 strerror 函数

目录 学习strerror函数 使用strerror函数 学习strerror函数 库函数在执行的时候&#xff0c;发生了错误&#xff0c;会将这个错误码存放在errno这个变量中&#xff0c;而errno是C语言提供的一个全局变量 而strerror函数是一个错误报告函数&#xff0c;可以将对应的错误码转…

roomformer-端到端矢量检测模型

论文&#xff1a;Connecting the Dots: Floorplan Reconstruction Using Two-Level Queries 论文地址&#xff1a;https://arxiv.org/pdf/2211.15658 code&#xff1a;https://github.com/ywyue/RoomFormer or https://github.com/woodfrog/poly-diffuse 参考&#xff1a;ht…

指针的指针作为形参实测

1. VS2019里面创建C控制台工程 2. 代码 #include <iostream>uint8_t buf[3][10] { {1,2,3},{4,5,6,7,8},{9,0} }; uint8_t len1 3,len2 5,len3 2;void f1(uint8_t **dstBuf, uint8_t *dstLen) {*dstBuf buf[0];*dstLen len1; }void f2(uint8_t** dstBuf, uint8_t*…

密码学基础-数据加密

密码学基础-对称加密与非对称加密 概述 安全通常从四个方面来定义&#xff1a; 机密性完整性合法性&#xff08;可用性&#xff0c;合法的数据才可用&#xff09;不可否认性&#xff08;发送方不可否认发送过的消息&#xff0c;接收方不可否认接收过的消息&#xff09; 对当…

低代码: 开发难点分析,核心技术架构设计

开发难点分析 1 &#xff09;怎样实现组件 核心问题&#xff1a;编辑器 和 页面其实整个就是一系列元素构成的这些元素的自然应该抽象成组件&#xff0c;这些组件的属性应该怎样设计在不同的项目中怎样做到统一的使用 2 &#xff09;跨项目使用 在不同的项目中怎样做到统一的…

最强开源文生图模型一夜易主!SD一作、Stabililty AI核心成员Robin Rombach下场创业了,一出手就是王炸。

时隔4个月&#xff0c;开源文生图模型霸主Stable Diffusion原班人马再创业&#xff01;2024年8月1日官宣&#xff1a;Black Forest Labs成立&#xff0c;公司的第一个产品FLUX.1系列模型包含专业版、开发者版、快速版三种模型&#xff0c;效果直接秒杀Midjourney、DALL-E和Stab…

解决报错:AssertionError: Torch not compiled with CUDA enabled

首先查看自己的cuda是否可用 torch.cuda.is_available()这里我的cuda是不适配torch的&#xff0c;所以需要重新安装适配的torch 查看自己的cuda版本 方法1 方法2 在cmd处输入nvidia-smi 这样可以找到的自己的CUDA版本安装符合自己版本的pytorch 进入pytorch官网https://pyt…

双指针实现删除字符串中的所有相邻重复项

class Solution:def removeDuplicates(self, s: str) -> str:res list(s)slow fast 0length len(res)while fast < length:# 如果一样直接换&#xff0c;不一样会把后面的填在slow的位置res[slow] res[fast]# 如果发现和前一个一样&#xff0c;就退一格指针if slow …

app逆向实战:某监管app2.0.5版本ROOT检测绕过

本篇博客旨在记录学习过程&#xff0c;不可用于商用等其它途径 场景 如下图&#xff0c;在我们打开APP时页面提示如此样式说明被检测到ROOT了&#xff0c;这种情况下无法进入页面请求抓包。 查壳 如果这个APP没有加固&#xff0c;那我们可以通过反编译修改检测的代码或者F…

Django与数据库

目录 创建项目app 路由子表 数据库 创建数据库 什么是ORM 定义数据库表 Django Admin 管理数据 过滤条件 代码直接生成HTML 使用模板 前后端分离架构 对资源的增删改查处理 列出客户 添加客户 临时取消 CSRF 校验 修改客户信息 删除客户 Django中ORM的处理 数据模…

QThread::wait: Thread tried to wait on itself

调用QThread的wait()方法时&#xff0c;报警&#xff1a;QThread::wait: Thread tried to wait on itself 原因&#xff1a;在自己的线程中调用线程的wait()方法。 解决方法&#xff1a;在线程外的其他线程中&#xff0c;调用线程的wait()方法。 示例代码如下&#xff1a; …

MATLAB学习之绘图篇(二维图)

目录 1.1基础图形绘制 1.1.1使用plot函数进行图形绘制 1.1.2为图像增加图例 1.1.3为图片增加标题以及坐标轴的描述 1.1.4控制坐标轴&#xff0c;边框以及网络 1.1.5在一个图像上绘制多条曲线 1.1.6在一个窗口绘制多个图像 1.1.7对图形的对象进行操作( 坐标属性&#xff…

LeetCode Hot100 二叉搜索树中第K小的元素

给定一个二叉搜索树的根节点 root &#xff0c;和一个整数 k &#xff0c;请你设计一个算法查找其中第 k 小的元素&#xff08;从 1 开始计数&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [3,1,4,null,2], k 1 输出&#xff1a;1示例 2&#xff1a; 输入&#xf…

【C/C++】C语言和C++实现Stack(栈)对比

我们初步了解了C&#xff0c;也用C语言实现过栈&#xff0c;就我们当前所更新过的有关C学习内容以栈为例子&#xff0c;来简单对比一下C语言和C。 1.C中栈的实现 栈的C语言实现在【数据结构】栈的概念、结构和实现详解-CSDN博客 &#xff0c;下面是C实现的栈&#xff0c; 在St…

ImageNet数据集和CIFAR-10数据集

一、为什么需要大量数据集 人工智能其实就是大数据的时代&#xff0c;无论是目标检测、图像分类、还是现在植入我们生活的推荐系统&#xff0c;“喂入”神经网络的数据越多&#xff0c;则识别效果越好、分类越准确。因此开源大型数据集的研究团队为人工智能的发展做了大量贡献…