c++学习(哈希)[21]

news2024/11/17 1:26:39

哈希

哈希表(Hash Table),也称为散列表,是一种常用的数据结构,用于实现键值对的存储和查找。它通过将键映射到一个索引位置来快速地访问和操作数据。

哈希表的基本思想是使用一个哈希函数将键映射到一个固定范围的整数,然后将这个整数作为索引来访问数组中的元素。这个数组通常被称为哈希表或哈希桶。当多个键映射到相同的索引位置时,称为哈希冲突,常用的解决冲突的方法有链地址法和开放地址法。

哈希表的优点包括:

  • 快速的查找和插入操作:通过哈希函数将键映射到索引位置,可以在常数时间内进行查找和插入操作。
  • 空间效率高:哈希表只需要存储键和值,不需要额外的空间来维护顺序。
  • 灵活性:哈希表可以存储任意类型的键值对,只要能够定义哈希函数和相等比较函数。

然而,哈希表也存在一些缺点:

  • 哈希函数的选择:选择一个好的哈希函数对于哈希表的性能至关重要,一个不好的哈希函数可能导致哈希冲突增多,影响性能。
  • 内存消耗:哈希表需要预先分配一定大小的数组来存储数据,如果数据量较大,可能会占用较多的内存空间。
  • 迭代顺序不确定:哈希表中的元素存储位置是根据哈希函数计算得到的,因此迭代哈希表的顺序是不确定的。

在C++中,可以使用std::unordered_map和std::unordered_set来实现哈希表。它们提供了高效的查找、插入和删除操作,并且可以存储任意类型的键值对或唯一元素。

哈希表的对应存储方式

哈希表的存储方式通常是通过数组来实现的,这个数组通常被称为哈希表或哈希桶。每个桶存储一个链表或者其他数据结构,用于解决哈希冲突。

具体来说,哈希表的存储方式可以分为以下几种:

  1. 链地址法(Separate Chaining):每个桶存储一个链表,当多个键映射到相同的索引位置时,将它们存储在同一个链表中。通过链表的方式解决了哈希冲突,可以存储任意数量的键值对。当链表过长时,可以考虑将链表转换为其他数据结构,如红黑树,以提高查找效率。
  2. 开放地址法(Open Addressing):当发生哈希冲突时,通过一定的规则找到下一个可用的空桶来存储冲突的键值对。常见的开放地址法包括线性探测、二次探测和双重哈希等。开放地址法的优点是节省了链表的空间开销,但当哈希表装载因子较高时,可能会导致查找效率下降。
  3. 建立公共溢出区(Overflow Area):当发生哈希冲突时,将冲突的键值对存储在一个公共的溢出区中,而不是在桶中。这样可以避免链表的使用,但需要额外的空间来存储溢出区。

无论使用哪种存储方式,哈希表的基本原理是通过哈希函数将键映射到索引位置,然后根据存储方式来处理哈希冲突。这样可以实现快速的查找、插入和删除操作,并且具有较低的冲突率和高效的空间利用率。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
开放地址法(Open Addressing)是一种解决哈希冲突的方法,在发生冲突时,通过一定的规则找到下一个可用的空桶来存储冲突的键值对。开放地址法的核心思想是,当发生冲突时,不使用链表等数据结构来存储冲突的元素,而是将其存储在其他的空桶中。具体的规则可以包括线性探测、二次探测、双重哈希等。例如,线性探测是指当发生冲突时,顺序地检查下一个桶,直到找到一个空桶来存储冲突的元素。

负载因子(Load Factor)是指哈希表中已经存储的键值对数量与哈希表容量的比值。负载因子可以用来衡量哈希表的装载程度。通常情况下,负载因子越大,表示哈希表中存储的键值对越多,装载程度越高。负载因子的计算公式为:

负载因子 = 已存储的键值对数量 / 哈希表容量

负载因子的选择对哈希表的性能有影响。当负载因子较小时,哈希表中的空桶较多,查找、插入和删除操作的效率较高。但随着负载因子的增加,哈希冲突的概率也会增加,可能导致查找、插入和删除操作的效率下降。因此,合适的负载因子选择是重要的,一般来说,负载因子的取值范围为0.7到0.8之间,可以在空间利用率和性能之间做出平衡。当负载因子超过某个阈值时,可以考虑进行哈希表的扩容,以保持较低的负载因子。

解决哈希冲突

闭散列(Closed Hashing)和开散列(Open Hashing)是两种不同的解决哈希冲突的方法。

闭散列,也称为封闭寻址法(Closed Addressing),是指当发生哈希冲突时,将冲突的键值对存储在哈希表的同一个桶中,通常使用链表或其他数据结构来存储冲突的元素。当需要查找、插入或删除一个键值对时,首先计算出它的哈希值,然后在对应的桶中查找或操作。闭散列的优点是可以存储任意数量的键值对,但当链表过长时,可能会导致查找效率下降。

开散列,也称为开放寻址法(Open Addressing),是指当发生哈希冲突时,通过一定的规则找到下一个可用的空桶来存储冲突的键值对。开散列的核心思想是,当发生冲突时,不使用链表等数据结构来存储冲突的元素,而是将其存储在其他的空桶中。具体的规则可以包括线性探测、二次探测、双重哈希等。开散列的优点是节省了链表的空间开销,但当哈希表装载因子较高时,可能会导致查找效率下降。

解决哈希冲突的常用方法包括:

  1. 链地址法(Chaining):当发生哈希冲突时,将冲突的键值对存储在同一个桶中,通过链表或其他数据结构连接冲突的元素。

  2. 开放地址法(Open Addressing):当发生哈希冲突时,通过一定的规则找到下一个可用的空桶来存储冲突的键值对。常见的开放地址法包括线性探测、二次探测、双重哈希等。

  3. 再哈希法(Rehashing):当发生哈希冲突时,使用另一个哈希函数计算出一个新的哈希值,然后将冲突的键值对存储在新的位置上。

  4. 建立公共溢出区(Overflow Area):当发生哈希冲突时,将冲突的键值对存储在一个公共的溢出区中,通过其他方式来记录溢出区的元素。

  5. 负载因子调整和扩容:通过调整负载因子的大小,或者在负载因子超过一定阈值时进行哈希表的扩容,以减少哈希冲突的概率。

这些方法各有优缺点,适用于不同的场景和需求。选择合适的解决方法需要考虑哈希表的装载因子、数据分布情况、性能要求等因素。

#pragma once
#include <vector>


namespace OpenAddress
{

	enum State
	{
		EMPTY,
		EXIST,
		DELETE
	};

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

	template<class K, class V>
	class HashTable
	{
	public:
		bool Insert(const pair<K, V>& kv)
		{
			if (Find(kv.first))
				return false;

			// 负载因子超过0.7就扩容
			//if ((double)_n / (double)_tables.size() >= 0.7)
			if (_tables.size() == 0 || _n * 10 / _tables.size() >= 7)
			{
				//size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				//vector<HashData> newtables(newsize);
				 遍历旧表,重新映射到新表
				//for (auto& data : _tables)
				//{
				//	if (data._state == EXIST)
				//	{
				//		// 重新算在新表的位置
				//		size_t i = 1;
				//		size_t index = hashi;
				//		while (newtables[index]._state == EXIST)
				//		{
				//			index = hashi + i;
				//			index %= newtables.size();
				//			++i;
				//		}

				//		newtables[index]._kv = data._kv;
				//		newtables[index]._state = EXIST;
				//	}
				//}

				//_tables.swap(newtables);

				// 10:34继续
				size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				HashTable<K, V> newht;
				newht._tables.resize(newsize);

				// 遍历旧表,重新映射到新表
				for (auto& data : _tables)
				{
					if (data._state == EXIST)
					{
						newht.Insert(data._kv);
					}
				}

				_tables.swap(newht._tables);
			}

			size_t hashi = kv.first % _tables.size();

			// 线性探测
			size_t i = 1;
			size_t index = hashi;
			while (_tables[index]._state == EXIST)
			{
				index = hashi + i;
				index %= _tables.size();
				++i;
			}

			_tables[index]._kv = kv;
			_tables[index]._state = EXIST;
			_n++;

			return true;
		}

		HashData<K, V>* Find(const K& key)
		{
			if (_tables.size() == 0)
			{
				return false;
			}

			size_t hashi = key % _tables.size();

			// 线性探测
			size_t i = 1;
			size_t index = hashi;
			while (_tables[index]._state != EMPTY)
			{
				if (_tables[index]._state == EXIST
					&& _tables[index]._kv.first == key)
				{
					return &_tables[index];
				}

				index = hashi + i;
				index %= _tables.size();
				++i;

				// 如果已经查找一圈,那么说明全是存在+删除
				if (index == hashi)
				{
					break;
				}
			}

			return nullptr;
		}

		bool Erase(const K& key)
		{
			HashData<K, V>* ret = Find(key);
			if (ret)
			{
				ret->_state = DELETE;
				--_n;
				return true;
			}
			else
			{
				return false;
			}
		}

	private:
		vector<HashData<K, V>> _tables;
		size_t _n = 0; // 存储的数据个数

		//HashData* tables;
		//size_t _size;
		//size_t _capacity;
	};

	void TestHashTable1()
	{
		int a[] = { 3, 33, 2, 13, 5, 12, 1002 };
		HashTable<int, int> ht;
		for (auto e : a)
		{
			ht.Insert(make_pair(e, e));
		}

		ht.Insert(make_pair(15, 15));

		if (ht.Find(13))
		{
			cout << "13在" << endl;
		}
		else
		{
			cout << "13不在" << endl;
		}

		ht.Erase(13);

		if (ht.Find(13))
		{
			cout << "13在" << endl;
		}
		else
		{
			cout << "13不在" << endl;
		}
	}
}

namespace HashBucket
{
	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 HashTable
	{
		typedef HashNode<K, V> Node;
	public:
		~HashTable()
		{
			for (auto& cur : _tables)
			{
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}

				cur = nullptr;
			}
		}

		Node* Find(const K& key)
		{
			if (_tables.size() == 0)
				return nullptr;

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

				cur = cur->_next;
			}

			return nullptr;
		}

		bool Erase(const K& key)
		{
			size_t hashi = key % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_kv.first == 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;
		}

		bool Insert(const pair<K, V>& kv)
		{
			if (Find(kv.first))
			{
				return false;
			}

			// 负载因因子==1时扩容
			if (_n == _tables.size())
			{
				/*size_t newsize = _tables.size() == 0 ? 10 : _tables.size()*2;
				HashTable<K, V> newht;
				newht.resize(newsize);
				for (auto cur : _tables)
				{
					while (cur)
					{
						newht.Insert(cur->_kv);
						cur = cur->_next;
					}
				}

				_tables.swap(newht._tables);*/

				size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				vector<Node*> newtables(newsize, nullptr);
				//for (Node*& cur : _tables)
				for (auto& cur : _tables)
				{
					while (cur)
					{
						Node* next = cur->_next;

						size_t hashi = cur->_kv.first % newtables.size();

						// 头插到新表
						cur->_next = newtables[hashi];
						newtables[hashi] = cur;

						cur = next;
					}
				}

				_tables.swap(newtables);
			}

			size_t hashi = kv.first % _tables.size();
			// 头插
			Node* newnode = new Node(kv);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;

			++_n;
			return true;
		}

	private:
		vector<Node*> _tables; // 指针数组
		size_t _n = 0; // 存储有效数据个数
	};

	void TestHashTable1()
	{
		int a[] = { 3, 33, 2, 13, 5, 12, 1002 };
		HashTable<int, int> ht;
		for (auto e : a)
		{
			ht.Insert(make_pair(e, e));
		}

		ht.Insert(make_pair(15, 15));
		ht.Insert(make_pair(25, 25));
		ht.Insert(make_pair(35, 35));
		ht.Insert(make_pair(45, 45));
	}

	void TestHashTable2()
	{
		int a[] = { 3, 33, 2, 13, 5, 12, 1002 };
		HashTable<int, int> ht;
		for (auto e : a)
		{
			ht.Insert(make_pair(e, e));
		}

		ht.Erase(12);
		ht.Erase(3);
		ht.Erase(33);
	}
}
#include <iostream>
#include <string>
#include <unordered_set>
#include <unordered_map>
#include <map>
#include <set>
#include <vector>
#include <time.h>
using namespace std;

void test_unordered_set1()
{
	unordered_set<int> s;
	s.insert(1);
	s.insert(3);
	s.insert(2);
	s.insert(7);
	s.insert(2);

	unordered_set<int>::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;
}

void test_unordered_map()
{
	string arr[] = { "", "", "ƻ", "", "ƻ", "ƻ", "", "ƻ", "㽶", "ƻ", "㽶", "" };
	map<string, int> countMap;
	for (auto& e : arr)
	{
		countMap[e]++;
	}

	for (auto& kv : countMap)
	{
		cout << kv.first << ":" << kv.second << endl;
	}
}
//
//int main()
//{
//	test_unordered_set1();
//	test_unordered_map();
//
//	return 0;
//}

//int main()
//{
//	const size_t N = 1000000;
//
//	unordered_set<int> us;
//	set<int> s;
//
//	vector<int> v;
//	v.reserve(N);
//	srand(time(0));
//	for (size_t i = 0; i < N; ++i)
//	{
//		v.push_back(rand());
//		//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 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 << endl;
//
//	cout << s.size() << endl;
//	cout << us.size() << endl << endl;;
//
//	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 << endl;
//	
//	return 0;
//}

#include "HashTable.h"

int main()
{
	HashBucket::TestHashTable2();

	return 0;
}

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

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

相关文章

自定义 View(六) 自定义评分星星

先看看效果图 1.自定义 View 的基本流程 创建 View Class创建 attr 属性文件&#xff0c;确定属性View Class 绑定 attr 属性onMeasure 测量onDraw 绘制onTouchEvent ( 用户交互需要处理 ) 1.1 创建 View Class package com.example.view_day05_ratingbar;import android.…

LabVIEW实现三相异步电机磁通模型

LabVIEW实现三相异步电机磁通模型 三相异步电动机由于经济和出色的机电坚固性而广泛用于工业化应用。这台机器的设计和驱动非常简单&#xff0c;但在控制扭矩和速度方面&#xff0c;它隐藏了相当大的功能复杂性。通过数学建模&#xff0c;可以理解机器动力学。 基于微分方程的…

day44-Custom Range Slider(自定义范围滑块)

50 天学习 50 个项目 - HTMLCSS and JavaScript day44-Custom Range Slider&#xff08;自定义范围滑块&#xff09; 效果 index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewp…

vue中tab隐藏display:none(v-show无效,v-if有效)

目录 背景 原因&#xff1a;display: table-cell>display:none 解决&#xff1a; 方法A.获取元素设置display&#xff08;适用于 简单场景&#xff09; 方法B.自定义tabs​​​​​​​ &#xff08;适用于 复杂场景&#xff09; 背景 内联样式(style“ ”) /this.$…

JVM简述

JDK&JRE&JVMJVM运行时内存结构图方法区堆区栈区程序计数器本地方法栈 JVM 的主要组成部分及其作用 JDK&JRE&JVM JVM就是java虚拟机&#xff0c;一台虚拟的机器&#xff0c;用来运行java代码 但并不是只有这台机器就可以的&#xff0c;java程序在运行时需要依赖…

Linux权限提升:自动化信息收集

在本文中&#xff0c;我们将介绍在基于Linux的设备上进行初始访问后&#xff0c;可用于后渗透阶段漏洞利用和枚举的一些自动化脚本。 ** 介绍** 大多数时候&#xff0c;当攻击者攻击Linux操作系统时&#xff0c;他们将获得基本的Shell&#xff0c;可以将其转换为TTY Shell或m…

apple pencil值不值得购买?便宜的电容笔推荐

如今&#xff0c;对ipad使用者而言&#xff0c;苹果原装的Pencil系列无疑是最佳的电容笔。只是不过这款电容笔的售价&#xff0c;实在是太高了&#xff0c;一般的用户都无法入手。因此&#xff0c;在具体的使用过程中&#xff0c;如何选用一种性能优良、价格低廉的电容笔是非常…

Jmeter+验证json结果是否正确小技巧

前言&#xff1a; 通过sql语句或者返回的参数&#xff0c;可以在查看结果树返回的结果中&#xff0c;用方法先跑一下验证是否取到自己想要的值 步骤&#xff1a; 1、添加查看结果树 2、跑出结果 3、在查看结果树中 text改成选Json Path Tester 返回的值如果是列表里面的字符…

英码“深元”智慧工厂解决方案,提升管理效率,开启生产新时代!

智慧工厂&#xff0c;作为数字化和智能化的代表&#xff0c;深度融合了边缘计算、物联网、大数据分析和人工智能等技术&#xff0c;为传统工厂管理和生产带来了深刻的影响。英码“深元”智慧工厂解决方案&#xff0c;利用智能终端——“深元”AI工作站实时采集传输现场视频&…

8个特别好用的矢量图软件,一定要收藏

在设计工作中&#xff0c;矢量图软件能帮助设计师绘制出具有更高质量&#xff0c;更高清晰度的图画作品。本文整理了市面上8个好用的矢量图软件&#xff0c;一起来看看吧&#xff01; 1、即时灵感 即时灵感是基于云端运行的矢量图软件&#xff0c;完全可以满足运营、产品经理…

荣登央视,智慧集中供冷,未来空调技术的新趋势

我们上了央视了&#xff01;这不是开玩笑也不是蹭热度&#xff0c;最近我们做的一个项目被作为正向报导了&#xff0c;可以说是一件引以为傲的事情&#xff0c;具体涉及的项目&#xff0c;就是作为未来空调技术的集中供冷系统。 今年夏天&#xff0c;想必大家也都感觉到了&…

【phpstudy】Apache切换Nginx报错nginx: [emerg] CreateFile()

【phpstudy】Apache切换Nginx报错nginx: [emerg] CreateFile() 报错内容如下&#xff1a; nginx: [emerg] CreateFile() “D:/phpstudy_pro/WWW/www.xxx.com/nginx.htaccess” failed (2: The system cannot find the file specified) in D:\phpstudy_pro\Extensions\Nginx1.…

【WebGIS实例】(10)Cesium开场效果(场景、相机旋转,自定义图片底图)

效果 漫游效果视频&#xff1a; 【WebGIS实例】&#xff08;10&#xff09;Cesium开场效果&#xff08;场景、相机 点击鼠标后将停止旋转并正常加载影像底图&#xff1a; 代码 可以直接看代码&#xff0c;注释写得应该比较清楚了&#xff1a; /** Date: 2023-07-28 16:21…

接口自动化如何做?接口自动化测试- 正则用例参数化(实例)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 我们在做接口自动…

ElasticSearch基本使用--ElasticSearch文章一

文章目录 官网学习必要性elasticsearch/kibana安装版本数据结构说明7.x版本说明ElasticSearch kibana工具测试后续我们会一起分析 官网 https://www.elastic.co/cn/ 学习必要性 1、在当前软件行业中&#xff0c;搜索是一个软件系统或平台的基本功能&#xff0c; 学习Elastic…

树莓派微型web服务器——阶段设计报告

文章目录 1. 需求分析1.1 功能需求1.1.1 访问需求1.1.2 自定义域名需求1.1.3 下载公共文件需求1.1.4 用户体验需求 1.2 技术需求1.2.1 操作系统指令1.2.2 技术栈1.2.3 内网穿透 1.3 性能需求1.3.1 处理能力1.3.2 内存1.3.3 存储空间 2. 可行性分析2.1 硬件方面2.2 软件方面 3. …

Linux近两年高危漏洞修复过程记录

一、背景 2023年8月份&#xff0c;面对即将到来的“大运会”、“亚运会”&#xff0c;今年的例行安全护网阶段也将迎来新的挑战和时刻&#xff0c;为此相关部门发布了国家级实战攻防演练已进入紧急「备战」时刻&#xff01;这里我们主要说一下Linux OS层面的漏洞处理&#xff0…

STM32 CubeMX 定时器(普通模式和PWM模式)

STM32 CubeMX STM32 CubeMX 定时器&#xff08;普通模式和PWM模式&#xff09; STM32 CubeMXSTM32 CubeMX 普通模式一、STM32 CubeMX 设置二、代码部分STM32 CubeMX PWM模式一、STM32 CubeMX 设置二、代码部分总结 STM32 CubeMX 普通模式 一、STM32 CubeMX 设置 二、代码部分 …

数据库数据恢复-Syabse数据库存储页底层数据杂乱的数据恢复案例

数据库恢复环境&#xff1a; Sybase版本&#xff1a;SQL Anywhere 8.0。 数据库故障&#xff1a; 数据库所在的设备意外断电后&#xff0c;数据库无法启动。 错误提示&#xff1a; 使用Sybase Central连接后报错&#xff1a; 数据库故障分析&#xff1a; 经过北亚企安数据恢复…

【无标题】深圳卫视专访行云创新马洪喜:拥抱AI与云原生,深耕云智一体化创新

人工智能&#xff08;AI&#xff09;是引领新一轮科技革命和产业变革的重要驱动力。因此&#xff0c;深圳出台相关行动方案&#xff0c;统筹设立规模1,000亿元的人工智能基金群&#xff0c;引导产业集聚培育企业梯队&#xff0c;积极打造国家新一代人工智能创新发展试验区和国家…