C++进阶--哈希

news2024/9/23 21:27:49

哈希概念

哈希(Hash)是一种常见的密码学技术和数据结构,它将任意长度的输入通过散列算法转换成固定长度的输出,这个输出被称为散列值或哈希值哈希函数是一种单向函数,即从哈希值无法反推出原始输入值

在这里插入图片描述
哈希函数具有以下特点

  1. 唯一性:相同的输入一定会得到相同的输出,保证了相同数据的哈希值是唯一的。
  2. 固定长度:不论输入的长度是多少,哈希函数都能生成固定长度的哈希值。
  3. 高效性:哈希函数计算速度快,适用于大规模数据的处理。
  4. 不可逆性:从哈希值无法推导出输入的原始数据,即使输入数据的微小变化也会导致输出值的巨大变化,保证了数据的安全性和完整性。

哈希冲突

在这里插入图片描述
哈希冲突是指在使用哈希函数将数据映射到哈希表存储时,可能会出现两个或多个不同的键被映射到相同的哈希值的情况。由于哈希表的存储空间是有限的,而要存储的数据量可能很大,所以哈希冲突是不可避免的

哈希冲突可能会导致以下问题:

  1. 数据丢失:当两个键被映射到相同的哈希值时,只能存储其中一个键,另一个键的数据可能会丢失。
  2. 性能下降:哈希冲突会导致在查找、插入和删除操作时需要进行额外的处理,从而降低了哈希表的性能。

哈希冲突的解决

为了解决哈希冲突,常见的方法有以下四种:

  1. 开放定址法:在遇到哈希冲突时,寻找一个新的空闲的哈希地址来存储冲突的元素。常见的开放定址法包括线性探测法、二次探测法和双重散列法等。
  2. 链地址法(拉链法):将哈希表的每个槽(桶)设为链表头节点,每个链表存储哈希值相同的元素。当发生哈希冲突时,冲突的元素会被添加到对应槽的链表中。

开放地址法

线性探测和删除问题

线性探测当插入一个键值对时,如果发生哈希冲突,线性探测会尝试在哈希表中找到下一个可用的位置来存储冲突的数据
.
具体而言,线性探测的步骤如下:

  1. 当发生哈希冲突时,计算下一个位置,通常使用线性递增的方式,即当前位置加上一个固定的增量。
  2. 如果下一个位置为空闲(没有被占用),则将冲突的数据存储在该位置。
  3. 如果下一个位置仍然被占用,继续计算下一个位置,直到找到空闲位置或遍历整个哈希表。

删除问题:如果对于一个key值进行删除,直接表示位置为空;那么会对相同哈希值的key值产生影响。
如上面的5与25;通过我们的线性探测:

在这里插入图片描述
这样不符合我们的想法;所以这里还需要通过标记的方法,对删除过的key不能直接标记为空;

代码

template<class K>
struct Hashfunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};
//特化
template<>
struct Hashfunc<string>
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto a : s)
		{
			hash += a;
			hash *= 31;
		}
		return hash;
	}
};

namespace open_address
{
	enum State
	{
		EMPTY,
		EXIST,
		DELETE
	};

	template<class K, class V>
	struct HashData
	{
		pair<K, V> _data;
		State _state = EMPTY;
	};

	
	template<class K, class V, class Hash = Hashfunc<K>>
	class HashTable
	{
	public:
		HashTable(size_t size = 10)
		{
			_tables.resize(size);
		}
		HashData<K, V>* Find(const K& key)
		{
			Hash hs;
			size_t hashi = hs(key) % _tables.size();
			while (_tables[hashi]._state != EMPTY)
			{
				if (key == _tables[hashi]._data.first && _tables[hashi]._state == EXIST)
				{
					return &_tables[hashi];
				}
				hashi++;
				hashi %= _tables.size();
			}
			return nullptr;
		}
		bool Insert(const pair<K, V>& data)
		{
			//保证不能有相同的元素
			if (Find(data.first))
				return false;
			//扩容
			if (_n * 10 / _tables.size() >= 7)
			{
				HashTable<K, V> newHT(_tables.size() * 2);
				for (auto& a : _tables)
				{
					if (a._state == EXIST)
					{
						newHT.Insert(a._data);
					}
				}
				_tables.swap(newHT._tables);
			}
			Hash hs;
			//线性探测
			size_t hashi = hs(data.first) % _tables.size();
			while (_tables[hashi]._state == EXIST)
			{
				hashi++;
				hashi %= _tables.size();
			}

			_tables[hashi]._data = data;
			_tables[hashi]._state = EXIST;
			_n++;

			return true;
		}

		bool Erase(const K& key)
		{
			HashData<K, V>* ret = Find(key);
			if (ret)
			{
				_n--;
				ret->_state = DELETE;
				return true;
			}
			else
			{
				return false;
			}
		}
	private:
		vector<HashData<K, V>> _tables;
		size_t _n = 0; //实际存储的数据个数
	};

	void TestHT1()
	{
		int a[] = { 1,5,25,35,7,44,17,37 };
		HashTable<int, int> ht;
		//插入到哈希表
		for (auto e : a)
		{
			ht.Insert(make_pair(e, e));
		}
		//遍历和打印状态
		for (auto e : a)
		{
			auto ret = ht.Find(e);
			if (ret)
			{
				cout << ret->_data.first << ":Exist" << endl;
			}
			else
			{
				cout << ret->_data.first << ":Delete" << endl;
			}
		}
		cout << endl;

		ht.Erase(44);
		ht.Erase(25);
		for (auto e : a)
		{
			auto ret = ht.Find(e);
			if (ret)
			{
				cout << ret->_data.first << ":Exist" << endl;
			}
			else
			{
				cout << e << ":Delete" << endl;
			}
		}
		cout << endl;
	}

	struct Date
	{
		int _year;
		int _month;
		int _day;
	};

	struct HashFuncDate
	{
		size_t operator()(const Date& d)
		{
			size_t hash = 0;
			hash += d._year;
			hash *= 31;

			hash += d._month;
			hash *= 31;

			hash += d._day;
			hash *= 31;
			return hash;
		}
	};

	struct Person
	{
		string _name;
		string _id;
		string _tel;
		int _age;
		string _class;
		string _address;
	};
	struct HashFuncPerson
	{
		size_t operator()(const Person& p)
		{
			size_t hash = 0;
			for (auto e : p._id)
			{
				hash += e;
				hash *= 31;
			}
			return hash;
		}
	};
	void TestHT2()
	{
		HashTable<string, string> ht;
		ht.Insert(make_pair("length", "长度"));
		ht.Insert(make_pair("data", "数据"));

		HashTable<Person, int, HashFuncPerson> ht1;
		HashTable<Date, string, HashFuncDate> ht2;
	}
}

解释

在这里插入图片描述

负载因子负载因子(load factor)是指哈希表中已经存储的元素数量与哈希表容量之比。它可以用来衡量哈希表的填充程度。一般情况下,负载因子越高,表示哈希表中已经存储的元素越多,哈希冲突的概率可能会增加,因此需要进行扩容操作来降低冲突。
.
负载因子通常用一个小数来表示,例如0.75表示哈希表已经存储的元素数量占总容量的75%。负载因子的取值范围通常是0到1之间。
.
具体计算哈希表的负载因子可以根据实际情况来决定。一般来说,当负载因子超过一定阈值(如0.75),就需要考虑进行哈希表的扩容操作,以保持哈希表的性能和效率。
.
记住,哈希表的负载因子并非越高越好。过高的负载因子可能会导致哈希冲突增加,从而影响哈希表的效率;过低的负载因子可能会存在哈希的底层空间过大,插入的key值远低于底层空间大小,这样会浪费不必要的空间。因此,在设计哈希表时,需要根据实际情况选择合适的负载因子阈值,以平衡空间利用率和性能要求。

在这里插入图片描述
在这里插入图片描述

测试1
在这里插入图片描述
在这里插入图片描述
测试2
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

对于Date和Person

在这里插入图片描述

链地址法

代码

namespace hash_bucket
{
	template <class K,class V>
	struct HashNode
	{
		HashNode<K, V>* _next;
		pair<K, V> _kv;

		HashNode(const pair<K, V>& kv)
			:_next(nullptr),
			_kv(kv)
		{}
		
	};

	template<class K,class V,class Hash=Hashfunc<K>>
	class HashTable
	{
		typedef HashNode<K, V> Node;
	public:
		HashTable()
		{
			_tables.resize(10, nullptr);
			_n = 0;
		}

		~HashTable()
		{
			for (size_t i= 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				_tables[i] = nullptr;
			}
		}

		Node* Find(const K& key)
		{
			Hash hs;
			size_t hashi = hs(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					return cur;
				}

				cur = cur->_next;
			}

			return nullptr;
		}

		bool Insert(const pair<K, V>& kv)
		{
			//查找是否有相同元素
			if (Find(kv.first))
				return false;

			Hash hs;
			//扩容
			if (_n == _tables.size())
			{
				vector<Node*> newTables(_tables.size() * 2, nullptr);
				for (size_t i = 0; i < _tables.size(); i++)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;

						size_t hashi = hs(cur->_kv.first) % newTables.size();
						cur->_next = newTables[hashi];
						newTables[hashi] = cur;

						cur = next;
					}
					_tables[i] = nullptr;
				}
				_tables.swap(newTables);
			}	

			size_t hashi = hs(kv.first) % _tables.size();
			Node* newnode = new Node(kv);

			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;

			++_n;
			return true;
		}

		bool Erase(const K& key)
		{
			Hash hs;
			size_t hashi = hs(key) % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_kv.first==key)
				{
					if (prev)
					{
						prev->_next = cur->_next;
					}
					else
					{
						_tables[hashi] = cur->_next;
					}
					delete cur;
					_n--;
					return true;
				}
				prev = cur;
				cur = cur->_next;
			}
			return false;
		}
		void Print()
		{
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				while (cur)
				{
					
					cout << "哈希值:" << i << "--" << "data:" << cur->_kv.first << endl;
					cur = cur->_next;
				}
			}
		}

		void Some()
		{
			size_t bucketSize = 0;//哈希桶数
			size_t maxBucketLen = 0;//最大哈希桶高度
			size_t sum = 0;//总数
			double averageBucketLen = 0;//哈希桶评价高度

			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				if (cur)
				{
					++bucketSize;
				}

				size_t bucketLen = 0;
				while (cur)
				{
					++bucketLen;
					cur = cur->_next;
				}

				sum += bucketLen;

				if (bucketLen > maxBucketLen)
				{
					maxBucketLen = bucketLen;
				}
			}

			averageBucketLen = (double)sum / (double)bucketSize;

			printf("load factor:%lf\n", (double)_n / _tables.size());//负载因子
			printf("哈希桶数:%d\n", _tables.size());
			printf("节点总数:%d\n", bucketSize);
			printf("最大哈希桶高度:%d\n", maxBucketLen);
			printf("平均哈希桶高度:%lf\n\n", averageBucketLen);
		}
	private:
		vector<Node*> _tables;
		size_t _n;
	};

	void TestHT1()
	{
		HashTable<int, int> ht;
		int a[] = { 1,4,24,34,7,44,17,37 };
		for (auto e : a)
		{
			ht.Insert(make_pair(e, e));
		}

		//插入测试打印
		ht.Print();
		cout << endl;
		//扩容后查看对应的哈希值
		ht.Insert(make_pair(5, 5));
		ht.Insert(make_pair(15, 15));
		ht.Insert(make_pair(25, 25));
		ht.Print();
		cout << endl;
		//删除测试
		ht.Erase(5);
		ht.Erase(15);
		ht.Erase(25);
		ht.Erase(35);

		ht.Print();
	}

	void TestHT2()
	{
		const size_t N = 100000;

		unordered_set<int> us;
		set<int> s;
		HashTable<int, int> ht;

		vector<int> v;
		v.reserve(N);
		srand(time(0));
		for (size_t i = 0; i < N; ++i)
		{
			//v.push_back(rand()); // N比较大时,重复值比较多
			//v.push_back(rand() + i); // 重复值相对少
			v.push_back(i); // 没有重复,有序
		}

		size_t begin1 = clock();
		for (auto e : v)
		{
			s.insert(e);
		}
		size_t end1 = clock();
		cout << "set insert:" << end1 - begin1 << endl;

		size_t begin2 = clock();
		for (auto e : v)
		{
			us.insert(e);
		}
		size_t end2 = clock();
		cout << "unordered_set insert:" << end2 - begin2 << endl;

		size_t begin10 = clock();
		for (auto e : v)
		{
			ht.Insert(make_pair(e, e));
		}
		size_t end10 = clock();
		cout << "HashTbale insert:" << end10 - begin10 << endl << endl;

		size_t begin3 = clock();
		for (auto e : v)
		{
			s.find(e);
		}
		size_t end3 = clock();
		cout << "set find:" << end3 - begin3 << endl;

		size_t begin4 = clock();
		for (auto e : v)
		{
			us.find(e);
		}
		size_t end4 = clock();
		cout << "unordered_set find:" << end4 - begin4 << endl;

		size_t begin11 = clock();
		for (auto e : v)
		{
			ht.Find(e);
		}
		size_t end11 = clock();
		cout << "HashTable find:" << end11 - begin11 << endl << endl;

		cout << "插入数据个数:" << us.size() << endl << endl;
		ht.Some();

		size_t begin5 = clock();
		for (auto e : v)
		{
			s.erase(e);
		}
		size_t end5 = clock();
		cout << "set erase:" << end5 - begin5 << endl;

		size_t begin6 = clock();
		for (auto e : v)
		{
			us.erase(e);
		}
		size_t end6 = clock();
		cout << "unordered_set erase:" << end6 - begin6 << endl;

		size_t begin12 = clock();
		for (auto e : v)
		{
			ht.Erase(e);
		}
		size_t end12 = clock();
		cout << "HashTable Erase:" << end12 - begin12 << endl << endl;
	}
}

解释

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
测试
在这里插入图片描述

性能测试

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

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

相关文章

Android14 - AMS之Activity启动过程(1)

Android14 - AMS之Activity启动过程&#xff08;2&#xff09;-CSDN博客 ​​​​​​​ Android14 - AMS之Activity启动过程&#xff08;3&#xff09;-CSDN博客 我们以Context的startActivity场景&#xff08;option null&#xff0c; FLAG_ACTIVITY_NEW_TASK&#xff09;来…

C++类型转换及IO流(深度剖析)

文章目录 1. 前言2. C语言的类型转换3. C的强制类型转换3.1 static_cast3.2 reinterpret_cast3.3 const_cast3.4 dynamic_cast 4. RTTI&#xff08;了解&#xff09;5. C语言的输入输出及缓存区理解6. CIO流6.1 C标准IO流6.2 C文件IO流 7. stringstream的简单介绍 1. 前言 C语言…

机器学习-可解释性机器学习:支持向量机与fastshap的可视化模型解析

一、引言 支持向量机(Support Vector Machine, SVM)作为一种经典的监督学习方法&#xff0c;在分类和回归问题中表现出色。其优点之一是生成的模型具有较好的泛化能力和可解释性&#xff0c;能够清晰地展示特征对于分类的重要性。 fastshap是一种用于快速计算SHAP值&#xff08…

华曦传媒陆锋:数字媒体时代,社区电梯广告价值正在被重估

在数字化时代的浪潮中&#xff0c;电梯广告、停车场道闸广告、门禁灯箱广告等线下社区广告似乎面临着生存的挑战。 然而&#xff0c;这一传统广告形式展现出了惊人的韧性和价值。 比如&#xff0c;2023年上半年&#xff0c;作为行业龙头分众传媒&#xff0c;2023年上半年实现…

【Linux】多线程编程基础

&#x1f4bb;文章目录 &#x1f4c4;前言&#x1f33a;linux线程基础线程的概念线程的优缺点线程与进程的区别 线程的创建 &#x1f33b;linux线程冲突概念互斥锁函数介绍加锁的缺点 &#x1f4d3;总结 &#x1f4c4;前言 无论你是否为程序员&#xff0c;相信多线程这个词汇应…

小白也能在3分钟完成短剧解说的剪辑,这是真的!

3分钟的解说视频&#xff0c;真的需要1小时的手工剪辑吗&#xff1f; 生成解说视频需要经过素材准备、解说词创作、声音录制、视频剪辑和视频合成等多个步骤&#xff0c;每个步骤都需要投入一定的时间和精力&#xff0c;因此整个过程较为耗时耗力。 1. 素材准备&#xff1a; 需…

【LINUX笔记】驱动开发框架

应用程序调动驱动程序 驱动模块运行模式 模块加载-卸载 加载卸载注册函数 加载 驱动编译完成以后扩展名为.ko&#xff0c;有两种命令可以加载驱动模块&#xff1a; insmod和modprobe 驱动卸载 驱动注册注销 //查看当前已经被使用掉的设备号 cat /proc/devices 实现设备的具…

AI系统性学习06—开源中文语言大模型

1、ChatGLM ChatGLM-6B的github地址&#xff1a;https://github.com/THUDM/ChatGLM-6B ChatGLM-6B 是一个开源的、支持中英双语的对话语言模型&#xff0c;基于 General Language Model (GLM) 架构&#xff0c;具有 62 亿参数。结合模型量化技术&#xff0c;用户可以在消费级…

【Java Web基础】一些网页设计基础(二)

文章目录 1. Bootstrap导航栏设计1.1 代码copy与删减效果1.2 居中属性与底色设置1.3 占不满问题分析1.4 字体颜色、字体大小、字体间距设置1.5 修改超链接hover颜色&#xff0c;网站首页字体颜色 1. Bootstrap导航栏设计 1.1 代码copy与删减效果 今天设计导航栏&#xff0c;直…

第4关:创建工程项目表J,并插入数据

任务描述 工程项目表J由工程项目代码(JNO)、工程项目名(JNAME)、工程项目所在城市(CITY)组成。创建工程项目表J(JNO,JNAME,CITY)&#xff0c;并在J表中插入下图数据。 相关知识 1、MySQL创建表的基本语法如下&#xff1a; 其中&#xff0c;table_name 是要创建的表的名称&…

Hololens 2应用开发系列(4)——MRTK基础知识及配置文件配置(下)

Hololens 2应用开发系列&#xff08;4&#xff09;——MRTK基础知识及配置文件配置&#xff08;下&#xff09; 一、前言二、边界系统&#xff08;Boundary&#xff09;三、传送系统&#xff08;Teleport&#xff09;四、空间感知系统&#xff08;Spatial Awareness&#xff09…

Pytorch神经网络-元组/列表如何喂到神经网络中

&#x1f4da;博客主页&#xff1a;knighthood2001 ✨公众号&#xff1a;认知up吧 &#xff08;目前正在带领大家一起提升认知&#xff0c;感兴趣可以来围观一下&#xff09; &#x1f383;知识星球&#xff1a;【认知up吧|成长|副业】介绍 ❤️感谢大家点赞&#x1f44d;&…

设计编程网站集:生活部分:饮食+农业,植物(暂记)

这里写目录标题 植物相关综合教程**大型植物&#xff1a;****高大乔木&#xff08;Trees&#xff09;&#xff1a;** 具有坚硬的木质茎&#xff0c;通常高度超过6米。例如&#xff0c;橡树、松树、榉树等。松树梧桐 **灌木&#xff08;Shrubs&#xff09;&#xff1a;** 比乔木…

基于Jenkins + Argo 实现多集群的持续交付

作者&#xff1a;周靖峰&#xff0c;青云科技容器顾问&#xff0c;云原生爱好者&#xff0c;目前专注于 DevOps&#xff0c;云原生领域技术涉及 Kubernetes、KubeSphere、Argo。 前文概述 前面我们已经掌握了如何通过 Jenkins Argo CD 的方式实现单集群的持续交付&#xff0c…

基于Springboot的在线投稿系统+数据库+免费远程调试

项目介绍: Javaee项目&#xff0c;springboot项目。采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring SpringBoot Mybatis VueMavenLayui来实现。MySQL数据库作为系统数据储存平台&a…

Java安全 反序列化(3) CC1链-TransformedMap版

Java安全 反序列化(3) CC1链-TransformedMap版 本文尝试从CC1的挖掘思路出发&#xff0c;理解CC1的实现原理 文章目录 Java安全 反序列化(3) CC1链-TransformedMap版配置jdk版本和源代码配置前记 为什么可以利用一.CC链中的命令执行我们可以尝试一下通过InvokerTransformer.tr…

分布式异步任务框架celery

Celery介绍 github地址&#xff1a;GitHub - celery/celery: Distributed Task Queue (development branch) 文档地址&#xff1a;Celery - Distributed Task Queue — Celery 5.3.6 documentation 1.1 Celery是什么 celery时一个灵活且可靠的处理大量消息的分布式系统&…

数据库关系运算理论:传统的集合运算概念解析

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

如何在wps的excel表格里面使用动态gif图

1、新建excel表格&#xff0c;粘贴gif图到表格里面&#xff0c;鼠标右键选择超链接。 找到源文件&#xff0c; 鼠标放到图片上的时候&#xff0c;待有个小手图标&#xff0c;双击鼠标可以放大看到动态gif图。 这种方式需要确保链接的原始文件位置和名称不能变化&#xff01;&a…

网工内推 | 云计算工程师,HCIE认证优先,最高18k*14薪

01 杭州中港科技有限公司 招聘岗位&#xff1a;云计算工程师 职责描述&#xff1a; 1、承担云计算相关工程交付、业务上云及售前测试&#xff0c;从事虚拟化、桌面云、存储、服务器、数据中心、大数据、相关产品的工程项目交付或协助项目交付。 2、承担云计算维护工程师职责&…