哈希的基本概念(开散列和闭散列)(附代码)

哈希

  • 哈希概念
    • 哈希冲突
    • 哈希函数
      • 常见的哈希函数
    • 哈希冲突的解决
      • 闭散列
      • 开散列

哈希概念

传统的查找函数,搜索的效率取决于比较的次数。而hash算法:在理想情况下,可以不经过任何比较,一次就能得到要搜索的结果。
存储结构:通过某种(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,所以在查找时,通过该映射函数可以很快找到该元素。

  • 插入元素
    根据待插入元素的关键字,以此函数计算出该元素的存储位置并按此位置进行存放。
  • 搜索元素
    对元素的关键码进行同样的计算,把求得的函数值当作元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功。

该方法就是哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表。
大致结构如下图所示:

在这里插入图片描述

哈希冲突

不同的关键词通过相同的哈希函数映射到同一个地址,该现象叫做哈希冲突/哈希碰撞。
如:元素11再插入上图中,就会和元素1产生碰撞。
1和11就是同义词

哈希函数

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

  • 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1之间
  • 哈希函数计算出来的地址能均匀分布在整个空间中
  • 哈希函数应比较简单

常见的哈希函数

  1. 直接定址法(常用)
    取关键字的某个线性函数为散列地址:Hash(Key)= A*Key+B
    优点:简单、均匀
    缺点:需要事先知道关键字的分布情况
    使用场景:适合查找比较小的连续的情况

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

  3. 平方取中法
    假设关键字是1234,对它的平方就是1522756,抽取中间3位227作为哈希地址;
    再比如关键字4321,平方是18671041,抽取中间的三位671或者710作为哈希地址。
    平方取中法适合:不知道关键字分布,而位数又不是很大的情况。

  4. 折叠法
    折叠法是将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这几部分叠加求和,并按散列表表长,取后几位作为散列地址。

  5. 随机数法
    选择一个随机函数,去关键字的随机函数值为它的哈希地址,即H(key) = random(key),其中random为随机数函数。
    通常应用于关键字不等时采用此法。

  6. 数学分析法
    在这里插入图片描述
    在这里插入图片描述

哈希冲突的解决

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

闭散列

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

  1. 线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。
  2. 闭散列处理冲突时,不能随便删除物理哈希表中已有的元素,若直接删除元素会影响其他元素的搜索。

闭散列线性探测实现:

	enum class State{EMPTY,EXIST,DELETE};template<class K, class V>struct Elem   //数据类型{pair<K, V> _data;State _state=State::EMPTY;};template<class K, class V>class Hashtables{public:bool insert(const pair<K, V>& data){if (Find(data.first))return false;//如果不满足负载,就扩容CheckSize();//求地址size_t hashi = data.first % _tables.size(); //不能是capacity,vector天然不支持//插入while(_tables[hashi]._state == State::EXIST){hashi++;   //线性探测法hashi %= _tables.size();}_tables[hashi]._data = data;_tables[hashi]._state = State::EXIST;n++;return true;}不方便删除接口//pair<K, V>* Find(const K& key)//{//	if (_tables.size() == 0)//	{//		return nullptr;//	}//	size_t hashi = key % _tables.size();//	size_t i = 1;//	while (_tables[hashi]._state==State::EXIST||_tables[hashi]._state==State::DELETE)  //存在或删除,不影响探测//	{//		if (_tables[hashi]._state == State::EXIST//			&& _tables[hashi]._data.first != key)//		{//			hashi = hashi + i;//			hashi %= _tables.size();//			i++;//		}//		else//		{//			return &_tables[hashi]._data;//		}//		if (hashi == key % _tables.size())  //转一圈也没找到//		{//			return nullptr;//		}//	}//	//	return nullptr;//}Elem<K,V>* Find(const K& key){if (_tables.size() == 0){return nullptr;}size_t hashi = key % _tables.size();size_t i = 1;//逻辑有问题while (_tables[hashi]._state == State::EXIST || _tables[hashi]._state == State::DELETE)  //存在或删除,不影响探测{if (_tables[hashi]._state == State::EXIST && _tables[hashi]._data.first == key){return &_tables[hashi];}else    //接着查找{hashi = hashi + i;hashi %= _tables.size();}if (hashi == key % _tables.size())  //转一圈也没找到,说明全是存在+删除{return nullptr;}}return nullptr;}bool erase(const K& key){auto it = Find(key);if (!it){return false;}//错了!!!!!!!!!!!!!!!//it._state == State::DELETE;it->_state = State::DELETE;n--;   //别忘记计数减一return true;}private:void CheckSize(){if (_tables.size()==0 ||(double)n / (double)_tables.size() > 0.7){int newcapacity = (_tables.size() == 0 ? 10 : _tables.size() * 2);vector<Elem<K, V>> newTables(newcapacity);//再次重新插入for (auto& e : _tables){if (e._state == State::EXIST){size_t hashi = e._data.first % newTables.size();while (newTables[hashi]._state == State::EXIST){hashi++;hashi %= newTables.size();}newTables[hashi]._data = e._data;newTables[hashi]._state = State::EXIST;}}swap(_tables, newTables);}}private:vector<Elem<K, V>> _tables;   //哈希表size_t n=0;    //表示插入数据的大小};

线性探测优点:实现简单
缺点:一旦发生冲突,所有冲突连在一起,容易产生“堆积”数据。

也可以用二次探测解决即:线性探测由hashi = hashi+i -> hashi + i^2。


研究表明:当表长为质数且装载因子a不超过0.5时,新的表项一定能够插入,而且任何一个位置都不会被探查两次。因此只要表中有一半的空位置,就不会存在装满的问题。在搜索时可以不考虑表装满的情况,但在插入时必须确保表的装填因子不超过0.5,如果超了,必须考虑增容。

因此闭散列最大的缺陷:空间利用率低,这也是哈希的缺陷。

开散列

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

namespace HashBucket
{template<class K, class V>struct Elem{Elem(const pair<K, V>& data):_data(data),_next(nullptr){}pair<K, V> _data;Elem<K, V> *_next;};template<class K, class V>class HashTables{typedef Elem<K, V> Node;typedef pair<K, V> Data;//typedef HashTables<K, V> HashTables;public:bool insert(const Data& data){//负载因子过大就扩容CheckCapacity();//散列地址size_t hashi = data.first % _tables.size();//直接插入头插Node* newNode = new Node(data);newNode->_next = _tables[hashi];_tables[hashi] = newNode;n++;return true;}Node* find(const K& key){size_t hashi = key % _tables.size();Node* cur = _tables[hashi];while (cur){if (cur->_data.first == key){return cur;}cur = cur->_next;}return nullptr;}bool erase(const K& key){size_t hashi = key % _tables.size();Node* cur = _tables[hashi];Node* prev = nullptr;while (cur){if (cur->_data.first == key){if (!prev)   //prev为空,说明是在表上{_tables[hashi] = cur->_next;}else{prev->_next = cur->_next;}delete cur;return true;}prev = cur;cur = cur->_next;}return false;}private://扩容有问题,不应该插入一个就扩容,插入一个就扩容!!!!!!!!!!!!void CheckCapacity(){//表为空或者负载因子大于0.7了就扩容//if (!_tables.size() || ((n * 10) / (_tables.size() * 10) > 7))/*if (!_tables.size() || ((n * 10) / _tables.size() > 7))*/if(n==_tables.size()){size_t newCapacity = (_tables.size() == 0 ? 10 : _tables.size() * 2);HashTables* newHashTable = new HashTables;newHashTable->_tables.resize(newCapacity);//将旧表的数据重新映射到新表//遍历旧表for (auto& e : _tables){if (e)  //不空,就有数据{Node* cur = e;  Node* next;   //下一个指针while (cur){size_t hashi = cur->_data.first % newCapacity;next = cur->_next;  //暂存下一个cur->_next = newHashTable->_tables[hashi];newHashTable->_tables[hashi] = cur;     //头插//newHashTable->n++;cur = next;}}}//swap(*newHashTable, *this);_tables.swap(newHashTable->_tables);}}private:vector<Node*> _tables;size_t n = 0;};}

哈希桶的增容问题:
在这里插入图片描述

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

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

相关文章

面向社交网络语言隐写分析

论文&#xff1a;Linguistic Steganalysis Toward Social Network 发表在&#xff1a;IEEE Transactions on Information Forensics & Security是网络与信息安全领域的国际两大顶级期刊之一&#xff0c;中国计算机学会&#xff08;CCF&#xff09;推荐的A类期刊&#xff0c…

​用技术的视角,去看一台家用 MPV 该有的水准

「MPV」一个在 2023 年之前都属于「小众」车型的品类。 但从 2023 年初开始&#xff0c;MPV 却变成了新能源&#xff0c;特别是高端新能源品牌必争的细分产品。 从岚图推梦想家开始&#xff0c;到腾势 D9&#xff0c;再到极氪 009&#xff0c;最后到魏牌高山&#xff0c;标志…

【开源】基于JAVA语言的智慧社区业务综合平台

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 业务类型模块2.2 基础业务模块2.3 预约业务模块2.4 反馈管理模块2.5 社区新闻模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 业务类型表3.2.2 基础业务表3.2.3 预约业务表3.2.4 反馈表3.2.5 社区新闻表 四、系统展…

Java中的this和super

①this 在Java中&#xff0c;this关键字代表当前对象的引用。它可以用于以下几个方面&#xff1a; 引用当前对象的成员变量&#xff1a;使用this关键字可以引用当前对象的成员变量&#xff0c;以区分成员变量和方法参数或局部变量之间的命名冲突。例如&#xff0c;如果一个方法…

LeetCode做题总结 226. 翻转二叉树

226. 翻转二叉树 代码1 报错代码2 报错代码3 正确。 代码1 报错 class Solution {public TreeNode invertTree(TreeNode root) {// TreeNode _root root; // 这是在保证// _root preOrderTree(root);// return root;root preOrderTree(root);return root;}public TreeNode …

【机器学习300问】15、什么是逻辑回归模型?

一、逻辑回归模型是为了解决什么问题&#xff1f; 逻辑回归&#xff08;Logistic Regression&#xff09;是一种广义线性回归分析模型&#xff0c;尤其适用于解决二分类问题&#xff08;输出为两个类别&#xff09;。 &#xff08;1&#xff09;二分类举例 邮件过滤&#xff…

Java Web(五)--DOM

介绍 DOM 全称是 Document Object Model 文档对象模型&#xff1b; DOM 是 W3C&#xff08;万维网联盟&#xff09;的标准。 DOM 定义了访问 HTML 和 XML 文档的标准&#xff1a; "W3C 文档对象模型 &#xff08;DOM&#xff09; 是中立于平台和语言的接口&#xff0…

pdf怎么转换成jpg图片?pdf转图片工具用它就够了

有时候&#xff0c;我们可能需要将pdf文档转换为图片格式&#xff0c;以便于文档的处理和管理。通过将pdf转换为图片&#xff0c;可以将每一页pdf转换为独立的图片文件&#xff0c;便于整理、存储和管理&#xff0c;如果您有多个PDF文件需要转换成图片&#xff0c;可以批量pdf转…

Modern C++ std::bind的实现原理

1. 前言 前面写过《std::function从实践到原理》&#xff0c;管中规豹了std::function的一点点原理&#xff0c;不过还有一个与std::function密切相关的函数std::bind, 允许编程者绑定几个参数&#xff0c;本文着重介绍它的实现原理。不介绍一下它&#xff0c;有点吃肉不吃蒜味…

npm安装卡住问题(最新版)

npm安装卡住问题(最新版) 背景&#xff1a; ​ 最近这两天用npm安装一些包的时候&#xff0c;发现一直卡住&#xff1a; 报错&#xff1a; idealTree:npm: sill idealTree buildDeps之前能用的现在不能用了&#xff0c;我一想&#xff0c;是不是源头的问题&#xff0c;还真是…

C语言每日一题(48)回文链表

力扣 234 回文链表 题目描述 给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为回文链表。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,2,1] 输出&#xff1a;true示例 2&#xff1…

Redis性能运行参数的监测工具 - WGCLOUD

WGCLOUD是一款开源免费的运维监控平台&#xff0c;可以监测Redis的运行情况&#xff0c;比如redis的Key数量&#xff0c;过期Key数量&#xff0c;Redis的端口号&#xff0c;Redis的版本&#xff0c;同步状态&#xff0c;集群模式&#xff0c;使用内存等等数据 中间件Redis监测…

FinBert模型:金融领域的预训练模型

文章目录 模型及预训练方式模型结构训练语料预训练方式 下游任务实验结果实验一&#xff1a;金融短讯类型分类实验任务数据集实验结果 实验二&#xff1a;金融短讯行业分类实验任务数据集实验结果 实验三&#xff1a;金融情绪分类实验任务数据集实验结果 实验四&#xff1a;金融…

瑞_数据结构与算法_二叉搜索树

文章目录 1 什么是二叉搜索树1.1 二叉搜索树的特征1.2 前驱后继 2 二叉搜索树的Java实现2.1 定义二叉搜索树节点类BSTNode泛型key改进 2.2 实现查找方法get(int key)递归实现非递归实现 ★非递归实现 泛型key版本 2.3 实现查找最小方法min()递归实现非递归实现 ★ 2.4 实现查找…

Django从入门到精通(三)

目录 七、ORM操作 7.1、表结构 常见字段 参数 示例 7.2、表关系 一对多 多对多 第一种方式 第二种方式 7.3、连接MYSQL 7.4、数据库连接池 7.5、多数据库 读写分离 分库&#xff08;多个app ->多数据库&#xff09; 分库&#xff08;单app&#xff09; 注意…

狗东云搭建幻兽帕鲁(奶妈级别)

使用狗东云搭建幻兽帕鲁 同配置狗东云比腾讯云便宜&#xff0c;2核2G服务器仅50元1年&#xff0c;4核8G服务器458元1年&#xff0c;点击链接直达. 进入页面会跳转到注册&#xff0c;先注册账户&#xff0c;注册好后页面跳转&#xff0c;没有跳转点这里&#xff0c;选择页面左侧…

[AIGC 大数据基础] 浅谈hdfs

HDFS介绍 什么是HDFS&#xff1f; HDFS&#xff08;Hadoop Distributed File System&#xff09;是Apache Hadoop生态系统的一部分&#xff0c;是一个分布式文件系统。它被设计用于存储和处理大规模数据集&#xff0c;并且能够容错、高可靠和高性能地处理文件。 HDFS是为了支…

2024转行程序员的请注意:均月薪在40-70k

前言 2023年&#xff0c;对大多数行业来说都是不太好过的一年。 对程序员来说也是如此&#xff0c;很多粉丝朋友都在说android工作特别难找&#xff0c;一个岗位都是几千份简历........大家心里都是特别的焦虑&#xff0c;本以为2024年就业情况会有好转&#xff0c;但实际上并…

PHP - Yii2 异步队列

1. 前言使用场景 在 PHP Yii2 中&#xff0c;队列是一种特殊的数据结构&#xff0c;用于处理和管理后台任务。队列允许我们将耗时的任务&#xff08;如发送电子邮件、push通知等&#xff09;放入队列中&#xff0c;然后在后台异步执行。这样可以避免在处理大量请求时阻塞主应用…

[GXYCTF2019]BabySQli1

单引号闭合&#xff0c;列数为三列&#xff0c;但是没有期待的1 2 3回显&#xff0c;而是显示wrong pass。 尝试报错注入时发现过滤了圆括号&#xff0c;网上搜索似乎也没找到能绕过使用圆括号的方法&#xff0c;那么按以往爆库爆表爆字段的方法似乎无法使用了 在响应报文找到一…