哈希表的认识与实现

news2024/11/15 11:09:29

哈希的概念

可以不经过任何比较,一次直接从表中得到要搜索的元素。如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。

所以当我们设置这种数据结构时主要就是要找好存储数据插入的位置建立好关系。但是我们插入数据的映射关系可能并不是一一对应的,也可能是多对一的关系也称为哈希冲突。所以说我们就要想办法解决这些多个插入数据对应一个插入位置的数据(哈希冲突)的问题,因此我们就有对应的哈希函数。哈希函数可以帮助我们将插入数据与插入位置关联起来,而合理的哈希函数可以有效缓解哈希冲突的问题实际上解决哈希冲突是通过闭散列和开散列

哈希函数

  •  直接定址法
  •  除留余数法
  • 平方取中法
  •  折叠法
  • 随机数法
  • 数学分析法
     

以上常用的方法也就是直接定值法和除留余数法。

直接定值法:取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B(适合查找比较小且连续的情况)

 除留余数法:设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址


除留余数法代码

namespace cr
{

	template<class K>
	struct Com_usual
	{
		size_t operator()(const K& key)
		{
			return key;
		}
	};
	template<>//模版特化
	struct Com_usual<string>//主要就是针对string类型
	{
		size_t operator()(const string& key)
		{
			 BKDR
			size_t ret = 0;
			for (auto e : key)
			{
				ret = ret * 31 + e;
			}
			return ret;
		}
	};

	enum state
	{
		empty,
		deleted,
		exit
	};

	template<class K, class V>
	struct Data
	{
		pair<K, V> _kv;//数据
		state _st;//空间对应的数据状态
	};

	template<class K, class V, class Com = Com_usual<K>>
	class HashTable
	{
	public:
		HashTable()
		{
			_table.resize(10);//保证size和capacity值一致
		}
		bool insert(const pair<K, V>& kv)
		{
			if (this->find(kv.first))
				return false;//找到就直接返回空

			if (_n * 10 / _table.size() == 7)
			{
				//扩容(不能直接扩,_table.size()改变了)
				HashTable<K, V, Com> newhash;
				newhash._table.resize(_table.size() * 2);
				for (int i = 0; i < _table.size(); i++)
				{
					if (_table[i]._st == exit)
					{
						newhash.insert(_table[i]._kv);
					}
				}
				_table.swap(newhash._table);//函数结束会释放交换后newtable的空间,也就是原来_table的空间
			}

			//插入数据
			Com co;
			int i = co(kv.first) % _table.size();
			while (_table[i]._st == exit)
			{
				i++;
				i %= _table.size();//防止越界
			}
			_table[i]._kv = kv;
			_table[i]._st = exit;
			++_n;//负载因子

			return true;
		}

		Data<K, V >* find(const K& key)
		{
			Com co;
			int i = co(key) % _table.size();
			while (_table[i]._st != empty)//因为设置的三个状态所以可以这以此判断
			{
				if (_table[i]._st == deleted);//删除只是调整了状态,数据没有清除
				else if (_table[i]._kv.first == key)
				{
					return &_table[i]; 
				}
				i++;
				i %= _table.size();//防止越界
			}
			return nullptr;//没找到
		}

		bool erase(const K& key)
		{
			Data<K, V >* ret = find(key);
			if (ret)
			{
				ret->_st = deleted;
				_n--;//有效数据个数

				//_table.erase(ret);//如果,不能仅仅调整数据状态
				//因为数据保留的话,find函数再次插入相同数据就插不进去
				//但是删除数据的话,顺序表的size会-1,所以也不能直接删除
				return 1;
			}
			return 0;
		}
		void Print()
		{
			for (int i = 0; i < _table.size(); i++)
			{
				if (_table[i]._st == exit)
				{
					//printf("[%d]->%d\n", i, _table[i]._kv.first);
					cout << "[" << i << "]->" << _table[i]._kv.first << ":" << _table[i]._kv.second << endl;
				}
				else if (_table[i]._st == empty)
				{
					printf("[%d]->\n", i);
				}
				else
				{
					printf("[%d]->D\n", i);
				}
			}

			cout << endl;
		}

	private:
		vector<Data<K, V>> _table;
		size_t _n = 0;//负载因子(保存着存储的关键字个数)用于计算空间利用率

	};
}

该方法的实质就是通过将插入的数据对应成整型,然后再模上空间的大小,存在对应映射的位置,如果该位置已经占了的话,就依次向后空的位置插入(线性探测)。因此我们在查找数据的时候就可以凭借插入数据的方式,查找该数据本该存放的位置,但是该位置可能已经被其他数据占据了,所以我们就可以判断后面的位置,直到找到空为止。


其实以上我们通过除留余数法来进行数据的插入,但是当我们遇到不同数据映射到同一位置时,我们采用的是:闭散列

哈希冲突解决(散列表)

解决哈希冲突两种常见的方法是:闭散列和开散列

闭散列

闭散列也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有
空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。

所以当我们查找数据时会采用线性探测从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止


而上面的代码也正是采用闭散列来存储数据,而查找数据也正是线性探测的方法。
 

开散列

开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地
址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链
接起来,各链表的头结点存储在哈希表中。
 

实现开散列 

namespace cr
{
	//对数据取模操作
	template<class K>
	struct Com_usual
	{
		size_t operator()(const K& key)//(不能重载string)
		{
			return key;
		}
	};
	template<>//模版特化
	struct Com_usual<string>
	{
		size_t operator()(const string& key)
		{
			 BKDR
			size_t ret = 0;
			for (auto e : key)
			{
				ret = ret * 31 + e;
			}
			return ret;
		}
	};

	template<class T>
	struct hash_Node
	{
		hash_Node(const T& data=T())
			:_data(data)
			,_next(nullptr)
		{}
		T _data;
		hash_Node* _next;

	};

	///迭代器实现
	template<class K, class T, class keyofT, class Com>//前置声明(不需要缺省参数)
	class hash_bucket;

	template<class K,class T, class ref,class ptr,class keyofT, class Com = Com_usual<K>>//
	struct hash_iterator
	{
		typedef hash_Node<T> Node;//类型重命名,带上模版参数
		typedef hash_iterator<K,T,ref,ptr,keyofT,Com> self;

		Node* _cur;
		const hash_bucket<K, T, keyofT, Com>* _hb;//哈希表指针,迭代器会修改数据,所以不能仅存表
		//哈希表与迭代器构成相互依赖关系
		//1.直接不传哈希表,传vector<Node*>
		//2.前置声明

		hash_iterator(Node* cur, hash_bucket<K, T, keyofT, Com>* hb)
			:_cur(cur)
			,_hb(hb)
		{}
		hash_iterator(Node* cur, const hash_bucket<K, T, keyofT, Com>* hb)
			:_cur(cur)
			, _hb(hb)//重载参数为const的拷贝构造函数
		{}

		bool operator!=(const self& s)
		{
			return _cur != s._cur;
		}
		ref operator*()
		{
			return _cur->_data;
		}
		ptr operator->()
		{
			return &_cur->_data;
		}
		const self& operator++()
		{
			keyofT my_key;Com co;
			int i = co(my_key(_cur->_data)) % (_hb->_hash.size());//求出当前迭代器指向位置
			
			if (_cur->_next)
				_cur = _cur->_next;
			else
			{
				i++;
				while (i<_hb->_hash.size())
				{
					if (_hb->_hash[i])
					{
						_cur = _hb->_hash[i];
						break;
					}
					i++;
				}
				//后面的数据都为空||已经找到后面的数据了
				if (i == _hb->_hash.size())
					_cur = nullptr;
			}
			return *this;
		}
		//const self& operator--()
		//{

		//}

	};

    //哈希桶
	template<class K,class T, class keyofT,class Com = Com_usual<K>>//
	class hash_bucket
	{
		template<class K, class T, class ref, class ptr, class KeyOfT, class Hash>
		friend struct hash_iterator;//由于迭代器部分中会访问哈希中的私有成员变量

	public:
		typedef hash_Node<T> Node;//类型重命名,带上模版参数
		typedef hash_iterator<K, T, T&, T*, keyofT, Com> iterator;
		typedef hash_iterator<K, T, const T&, const T*, keyofT, Com> const_iterator;

		iterator begin()
		{
			for (int i = 0; i < _hash.size(); i++)
			{
				if (_hash[i])
					return iterator(_hash[i], this);
			}
			return end();
		}
		iterator end()
		{
			return iterator(nullptr, this);
		}
		const_iterator cbegin()const//const修饰this
		{
			for (int i = 0; i < _hash.size(); i++)
			{
				if (_hash[i])
					return const_iterator(_hash[i], this);//迭代器构造时会传const this参数
			}
			return cend();
		}
		const_iterator cend()const
		{
			return const_iterator(nullptr, this);
		}

		hash_bucket()
		{
			_hash.resize(10);
		}
		~hash_bucket()//析构函数需要释放掉哈希桶里的节点
		{
			for (int i = 0; i < _hash.size(); i++)
			{
				while (_hash[i] != nullptr)
				{
					Node* tmp = _hash[i];
					_hash[i] = _hash[i]->_next;
					delete tmp;
				}
				_hash[i] = nullptr;
			}
		}

		pair<iterator,bool> insert(const T& data)
		{
			keyofT my_key;
			Com co;

			iterator rit = find(my_key(data));
			if (rit._cur)//为空就是找到了
				return make_pair(rit, false);
			//扩容
			if (_n == _hash.size())
			{
				vector<Node*> newhash;
				newhash.resize(_hash.size() * 2);//会自动调用自定义类型的构造函数
				//遍历旧表,将旧表的节点移到新表上
				for (int i = 0; i < _hash.size(); i++)
				{
					Node* cur = _hash[i];
					while (cur != nullptr)
					{
						Node* next = cur->_next;//提前保存好链表的下一个节点
						//链接
						int hashi = co(my_key(cur->_data)) % newhash.size();//找到新的表的映射位置处
						cur->_next = newhash[hashi];
						newhash[hashi] = cur;
						
						cur = next;
					}
					//将原旧表的vector的_hash[i]的指向依旧还是有指向的
					_hash[i] = nullptr;//置空以后再析构
					
				}
				_hash.swap(newhash);//交换,释放旧表
			}
			int i = co(my_key(data)) % _hash.size();
			Node* newnode = new Node(data);
			
			//头插
			newnode->_next = _hash[i];
			_hash[i] = newnode;
			++_n;

			return make_pair(iterator(newnode,this),true);
		}

		iterator find(const K& key)//找到了就返回找到的节点,没找到就新建节点
		{
			Com co; keyofT my_key;
			int i = co(key) % _hash.size();
			Node* cur = _hash[i];
			while (cur)
			{
				if (my_key(cur->_data) == key)
					return iterator(cur,this);
				cur = cur->_next;
			}
			return end();
		}

		bool erase(const K& key)
		{
			Com co; keyofT my_key;
			int i = co(key) % _hash.size();
			Node* cur = _hash[i];
			Node* pre = nullptr;
			while (cur)
			{
				if (my_key(cur->_data) == key)
				{
					if (pre == nullptr)//防止删除的正好是头结点
						_hash[i] = cur->_next;
					pre->_next = cur->_next;

					delete cur;
					return true;
				}
				pre = cur;
				cur = cur->_next;
			}
			return false;
		}

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

开散列适配unordered_map

#include"hash.h"

namespace cr
{
	template<class K,class V>
	class unordered_map
	{
		struct keyofT_map//仿函数取得key
		{
			const K& operator()(const pair<K,V>& kv)
			{
				return kv.first;
			}
		};
		
	public:
		typedef typename hash_bucket<K, pair<const K, V>, keyofT_map>::iterator iterator;
		typedef typename hash_bucket<K, pair<const K, V>, keyofT_map>::const_iterator const_iterator;

		iterator begin()
		{
			return _hb.begin();
		}
		iterator end()
		{
			return _hb.end();
		}
		const_iterator cbegin()const 
		{
			return _hb.cbegin();
		}
		const_iterator cend()const
		{
			return _hb.cend();
		}

		pair<iterator,bool> insert(const pair<K,V>& kv)
		{
			return _hb.insert(kv);
		}

		V& operator[](const K& key)
		{
			pair<iterator,bool> ret = insert(make_pair(key,V()));//缺省值
			//return ret.first._cur->_data.second;
			return (ret.first)->second;
		}

		bool erase(const K& key)
		{
			return _hb.erase(key);
		}
		iterator find(const K& key)
		{
			return _hb.find(key);
		}

	private:
		hash_bucket<K, pair<const K, V>,keyofT_map> _hb;
	};
}

开散列实现unordered_set

#include"hash.h"

namespace cr
{
	template<class K>
	class unordered_set
	{
		struct keyofT_set//仿函数取得key
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};

	public:
		typedef typename hash_bucket<K, K, keyofT_set>::const_iterator iterator;
		typedef typename hash_bucket<K, K, keyofT_set>::const_iterator const_iterator;

		iterator begin()
		{
			return _hb.begin();
		}
		iterator end()
		{
			return _hb.end();
		}
		const_iterator cbegin()const
		{
			return _hb.cbegin();
		}
		const_iterator cend()const
		{
			return _hb.cend();
		}

		pair<iterator, bool> insert(const K& key)
		{
			pair<typename hash_bucket<K, K, keyofT_set>::iterator, bool> ret = _hb.insert(key);
			//直接构造匿名对象		
			return pair<iterator, bool>(iterator(ret.first._cur, ret.first._hb), ret.second);
		}

		bool erase(const K& key)
		{
			return _hb.erase(key);
		}
		iterator find(const K& key)
		{
			return _hb.find(key);
		}

	private:
		hash_bucket<K, K,keyofT_set> _hb;
	};

}

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

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

相关文章

【数据结构】—搜索二叉树(C++实现,超详细!)

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;消えてしまいそうです—真夜中 1:15━━━━━━️&#x1f49f;──────── 4:18 &#x1f504; ◀️ ⏸ ▶️…

《微信小程序从入门到精通》---笔记1

小程序&#xff0c;我又来学习啦&#xff01;请多关照~ 项目驱动 小程序开发建议使用flex布局在小程序中&#xff0c;页面渲染和业务逻辑是分开的&#xff0c;分别运行在不同的线程中。Mini Program于2017年1月7号正式上线小程序的有点&#xff1a;跨平台、开发门槛低、开发周…

基于战争策略算法优化概率神经网络PNN的分类预测 - 附代码

基于战争策略算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于战争策略算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于战争策略优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神…

SSL握手失败的解决方案

一、SSL握手失败的原因&#xff1a; 1&#xff0c;证书过期&#xff1a;SSL证书有一个有效期限&#xff0c;如果证书过期&#xff0c;就会导致SSL握手失败。 2&#xff0c;证书不被信任&#xff1a;如果网站的SSL证书不被浏览器或操作系统信任&#xff0c;也会导致SSL握手失败…

(Matalb时序预测)GA-BP遗传算法优化BP神经网络的多维时序回归预测

目录 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 亮点与优势&#xff1a; 二、实际运行效果&#xff1a; 三、部分代码 四、本文代码数据说明手册分享&#xff1a; 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 本代码基于Matalb平台编译&am…

C#,《小白学程序》第四课:数学计算,总和与平均值

程序是 数据 计算 显示。 1 文本格式 /// <summary> /// 《小白学程序》第四课&#xff1a;数学计算 /// 这节课超级简单&#xff0c;就是计算成绩的平均值&#xff08;平均分&#xff09; /// 这个是老师们经常做的一件事。 /// </summary> /// <param name&…

C#,《小白学程序》第六课:队列(Queue)其二,队列的应用,编写《实时叫号系统》

医院里面常见的《叫号系统》怎么实现的&#xff1f; 1 文本格式 /// <summary> /// 下面定义一个新的队列&#xff0c;用于演示《实时叫号系统》 /// </summary> Queue<Classmate> q2 new Queue<Classmate>(); /// <summary> /// 《小白学程序…

超赞 | 7组优秀的金融APP界面设计案例分享!

随着各大银行陆续推出手机银行APP&#xff0c;金融APP在众多领域也开始崭露头角。然而&#xff0c;从用户体验的角度来看&#xff0c;金融APP设计与其他类型的APP设计有其独特之处。由于金融APP与人们的钱包息息相关&#xff0c;因此&#xff0c;其安全性、界面体验和操作反馈等…

DataFunSummit:2023年现代数据栈技术峰会-核心PPT资料下载

一、峰会简介 现代数据栈&#xff08;Modern Data Stack&#xff09;是一种集合了多种技术和工具的软件基础设施&#xff0c;旨在更好地管理和处理数据&#xff0c;并为企业提供数据驱动的洞察和决策。包含以下几个组件&#xff1a;数据采集、数据处理、数据存储、数据查询和分…

OpenCV快速入门:特征点检测与匹配

文章目录 前言一、角点检测1.1 角点特征1.1.1 角点特征概念1.1.2 角点的特点1.1.3 关键点绘制代码实现1.1.4 函数解析 1.2 Harris角点检测1.2.1 Harris角点检测原理1.2.2 Harris角点检测公式1.2.3 代码实现1.2.4 函数解析 1.3 Shi-Tomasi角点检测1.3.1 Shi-Tomasi角点检测原理1…

Qt项目打包发布超详细教程

https://blog.csdn.net/qq_45491628/article/details/129091320

Hibernate的三种状态

1.瞬时状态(Transient) 通过new创建对象后&#xff0c;对象并没有立刻持久化&#xff0c;他并未对数据库中的数据有任何的关联&#xff0c;此时java对象的状态为瞬时状态&#xff0c;Session对于瞬时状态的java对象是一无所知的&#xff0c;当对象不再被其他对象引用时&#xf…

AIGC 3D即将爆发,混合显示成为产业数字化的生产力平台

2023年&#xff0c;大语言模型与生成式AI浪潮席卷全球&#xff0c;以文字和2D图像生成为代表的AIGC正在全面刷新产业数字化。而容易为市场所忽略的是&#xff0c;3D图像生成正在成为下一个AIGC风口&#xff0c;AIGC 3D宇宙即将爆发。所谓AIGC 3D宇宙&#xff0c;即由文本生成3D…

【数据结构】二叉排序树(c风格、结合c++引用)

目录 1 基本概念 结构体定义 各种接口 2 二叉排序树的构建和中序遍历 递归版单次插入 非递归版单次插入 3 二叉排序树的查找 非递归版本 递归版本 4 二叉排序树的删除&#xff08;难点&#xff09; 1 基本概念 普通二叉排序树是一种简单的数据结构&#xff0c;节点的值…

【TL431+场效应管组成过压保护电路】2022-3-22

缘由这个稳压三极管是构成的电路是起到保护的作用吗&#xff1f;-硬件开发-CSDN问答

微服务实战系列之Nginx

前言 Nginx&#xff1f;写了那么多文章&#xff0c;为什么今天才轮到它的表演&#xff1f;那是因为它实在太重要了&#xff0c;值得大书特书&#xff0c;特别对待。 当我们遇到单点瓶颈&#xff0c;第一个idea是&#xff1f;Nginx&#xff1b; 当我们需要反向代理&#xff0c;…

弹窗msvcp140_1.dll丢失的解决方法,超简单的方法分享

在计算机使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中最常见的就是缺少某个文件的错误。最近&#xff0c;我在使用某些软件时&#xff0c;遇到了一个名为“msvcp140_1.dll”的错误提示。这个错误通常出现在运行某些程序时&#xff0c;由于缺少了msvcp140…

burp抓取雷电模拟器的数据包

文章目录 一、从burp中导出证书二、雷电模拟器的相关设置三、将burp的证书添加到模拟器的系统证书下四、安装ProxyDroid 所需软件 雷电模拟器版本&#xff1a;4.0.83Burp Suite Community Edition v2023.10.3.6ProxyDroid 起因&#xff1a;常规方法&#xff08;wifi处添加代理…

生态对对碰|华为OceanStor闪存存储与OceanBase完成兼容性互认证!

近日&#xff0c;北京奥星贝斯科技有限公司 OceanBase 数据库与华为技术有限公司 OceanStor Dorado 全闪存存储系统、OceanStor 混合闪存存储系统完成兼容性互认证。 OceanBase 数据库挂载 OceanStor 闪存存储做为数据盘和日志盘&#xff0c;在 OceanStor 闪存存储系统卓越性能…

“三个绝技“让项目经理轻松做好进度管理

大家好&#xff0c;我是老原。 我离开腾讯之后&#xff0c;曾经加入一家互联网创业公司。 要知道&#xff0c;当你在一个大公司的平台上做事做习惯之后&#xff0c;觉得一些流程都应该是严谨的、完备的、按计划进行的。 但是当时&#xff0c;经常出现一个致命问题——进度拖…