13【CPP】Hash(闭散列||开散列)

闭散列

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

线性探测

需要定义三个状态,空、删除、存在。因为如果不定义删除状态,那么在删除元素后就会对该元素后面的元素查找产生影响。

实现

#pragma once
#include<vector>
using namespace std;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:HashTable(){_tables.resize(10);}
public:bool Insert(const pair<K, V>& kv){//负载因子超过0.7就扩容if (_tables.size()==0||_n * 10 / _tables.size() >= 7){//扩容要开全新空间,重新算位置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);//vector底层直接交换指针就行}size_t hashi = kv.first % _tables.size();size_t i = 1;//控制步长size_t index = hashi;while (_tables[index]._state==EXIST)//如果存在hashi继续往后走{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 = kv.first % _tables.size();size_t i = 1;size_t index = hashi;while (_tables[index]._state != EMPTY){if (_tables[index]._kv.first == key){return &_tables[index];}index = hashi + i;index %= _tables.size();++i;}}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;//存储数据个数
};void Test_hashtable1()
{HashTable<int, int> ht;int a[] = { 4,14,24,34,5,7,1 };for (auto e : a){ht.Insert(make_pair(e, e));}ht.Insert(make_pair(13, 13));}

问题

上述代码查找存在问题,当插入数据后,扩容前,删除一部分数据再插入数据,并且数据正好占据了其他空位,导致表中状态除了存在就是删除。

HashData<K, V>* Find(const K& key){if (_tables.size() == 0)return false;size_t hashi = kv.first % _tables.size();size_t i = 1;size_t index = hashi;while (_tables[index]._state != EMPTY){if (_tables[index]._kv.first == key){return &_tables[index];}index = hashi + i;index %= _tables.size();++i;//如果已经查找了一圈,那么说明全是存在+删除if (index == hashi){break;}}}

缺陷

线性探测缺点:一旦发生哈希冲突,所有的冲突连在一起,容易产生数据“堆积”,即:不同关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降低。解决方案:二次探测、开散列。

开散列

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

实现

插入

namespace HashBucket
{template<class K,class V>struct HashNode{HashNode(const pair<K, V>& kv):_kv(kv){}struct HashNode<K, V>* _next=nullptr;pair<K, V> _kv;};template<class K,class V>class HashTable{typedef HashNode<K, V> Node;public:HashTable(){_tables.resize(10);}bool Insert(const pair<K, V>& kv){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;//存储的有效数据个数};
}

这里选择使用头插,时间复杂度o(1),省去了尾插找尾的步骤。hashtable成员变量_tables是一个指针数组,存放的是指向每一个节点链表头的指针。

负载因子

在这里插入图片描述
负载因子越大,冲突的概率越高,查找效率越低,空间利用率越高。
对于闭散列,我们采取当负载因子为1时进行扩容。

扩容

bool Insert(const pair<K, V>& kv){//负载因子等于1时扩容if (_n == _tables.size()){size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;vector<Node*> newtables(newsize, nullptr);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;}

这里扩容不是重新创一个一个节点,而是把原本_tables中的数据再重新遍历一遍依次插入到新开的vector中,最后实现交换。

加入仿函数/模版特化

如果类型是string或其他自定义类型那么无法计算key,那么可以利用仿函数转化成整形。
在这里插入图片描述
对于需要处理的string或自定义类型,传入自己写好的仿函数即可
在这里插入图片描述
仿函数中不一定要取第一个字符的ASCII码,可以取所有字符的和加起来的值去查找,这样可以大大提高效率
当然还有很多哈希算法更强,如BKDR哈希,详见字符串哈希算法
在这里插入图片描述

完整实现代码

#pragma once
#include<vector>
#include<iostream>
#include<string>using namespace std;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:HashTable(){_tables.resize(10);}public:bool Insert(const pair<K, V>& kv){//负载因子超过0.7就扩容if (_tables.size() == 0 || _n * 10 / _tables.size() >= 7){//扩容要开全新空间,重新算位置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);//vector底层直接交换指针就行}size_t hashi = kv.first % _tables.size();size_t i = 1;//控制步长size_t index = hashi;while (_tables[index]._state == EXIST)//如果存在hashi继续往后走{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]._kv.first == key){return &_tables[index];}index = hashi + i;index %= _tables.size();++i;//如果已经查找了一圈,那么说明全是存在+删除if (index == hashi){break;}}}bool Erase(const K& key){HashData<K, V>* ret = Find(key);if (ret){ret->_state = DELETE;--_n;return true;}else{return false;}}void Print()const{for (auto e : _tables){if(e._state==EXIST)cout << e._kv.first << " ";}cout << endl;}private:vector<HashData<K, V>> _tables;size_t _n = 0;//存储数据个数};void Test_hashtable1(){HashTable<int, int> ht;int a[] = { 4,14,24,34,5,7,1 };for (auto e : a){ht.Insert(make_pair(e, e));}ht.Insert(make_pair(13, 13));ht.Print();}
}namespace HashBucket
{template<class K,class V>struct HashNode{HashNode(const pair<K, V>& kv):_kv(kv){}struct HashNode<K, V>* _next=nullptr;pair<K, V> _kv;};template<class K>struct HashFunc{size_t operator()(const K& key){return key;}};//针对string模版特化template<>struct HashFunc<string>{size_t operator()(const string& s){//BKDR哈希size_t hash = 0;for (auto ch : s){hash += ch;hash *= 31;}return hash;}};template<class K,class V,class Hash=HashFunc<K>>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;}}HashTable(){_tables.resize(10);}bool Insert(const pair<K, V>& kv){//负载因子等于1时扩容Hash hash;if (_n == _tables.size()){size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;vector<Node*> newtables(newsize, nullptr);for (auto& cur : _tables){while (cur){Node* next = cur->_next;size_t hashi = hash(cur->_kv.first) % newtables.size();cur->_next = newtables[hashi];newtables[hashi] = cur;cur = next;}}_tables.swap(newtables);}size_t hashi = hash(kv.first) % _tables.size();//头插Node* newnode = new Node(kv);newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return true;}Node* Find(const K& key){Hash hash;size_t hashi =hash( 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){Hash hash;//不能向开散列一样find去删size_t hashi = hash(key) % _tables.size();Node* cur = _tables[hashi];//单链表删除需要一个prevNode* prev = nullptr;while (cur){if (cur->_kv.first == key){if (prev){prev->_next = cur->_next;}else{_tables[hashi]= cur->_next;}delete cur;return true;}else{prev = cur;cur = cur->_next;}}return false;}size_t MaxBucketSize(){size_t max = 0;for (size_t i=0;i<_tables.size();i++){size_t size = 0;auto cur = _tables[i];while (cur){++size;cur = cur->_next;}printf("[%d]->%d\n",i, size);if (size > max){max = size;}}return max;}void Print(){for ( auto cur : _tables){while (cur){std::cout << cur->_kv.first << " ";cur = cur->_next;}}cout << endl;}private:vector<Node*> _tables;size_t _n=0;//存储的有效数据个数};void Test_hashbucket1(){HashTable<int, int> ht;int a[] = { 4,14,24,34,5,7,1 };for (auto e : a){ht.Insert(make_pair(e, e));}ht.Insert(make_pair(13, 13));ht.Insert(make_pair(23, 23));ht.Insert(make_pair(33, 33));ht.Insert(make_pair(43, 43));ht.Print();ht.Erase(13);ht.Erase(33);ht.Print();}/*struct HashStr{size_t operator()(const string& s){size_t hash = 0;for (auto ch : s){hash += ch;hash *= 31;}return hash;}};*/void Test_hashbucket2(){HashTable<string, string> hts;hts.Insert(make_pair("mao", "毛"));hts.Insert(make_pair("dou", "豆"));hts.Print();}void Test_hashbucket3(){size_t N = 100000;HashTable<int, int> ht;srand(time(0));for (size_t i = 0; i < N; i++){size_t x = rand();ht.Insert(make_pair(x, x));}cout << ht.MaxBucketSize() << endl;}
}

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

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

相关文章

备战蓝桥杯————二分搜索(一)

引言 一、二分查找 基本概念 代码框架 二、二分查找 题目描述 解题思路及代码 结果展示 三、寻找左侧边界的二分搜索 使用背景 基本代码 引言 在计算机科学的世界里&#xff0c;二分查找算法无疑是一种经典且强大的工具。它以其高效的性能&#xff0c;在有序数据集中…

Windows10安装Docker

使用 PowerShell 启用 Hyper-V 以管理员身份打开 PowerShell 控制台。 运行以下命令&#xff1a; PowerShell复制 Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All如果无法找到此命令&#xff0c;请确保你以管理员身份运行 PowerShell。 安装…

Android大厂高级面试题灵魂100问,带你彻底弄明白

“2020年技术没有成长&#xff0c;我今年一定要好好努力学习&#xff01;” “在现在这个公司都工作了3年了&#xff0c;一毛钱工资都没有涨…” “年前真倒霉&#xff0c;老板嫌我工资高&#xff0c;被优化了&#xff0c;年后又遇到了疫情&#xff0c;现在都还没有找到合适的工…

从Win转Mac,我的感受如何

文章目录 前言MacBook优点美观动画流畅安装软件方便轻便、续航强大多数命令和Linux通用系统稳定、安全做工精美、视听体验好CPU性能较好触控板体验好 MacBook缺点缺乏部分软件部分操作逻辑不是很科学&#xff1f;玩不了多少游戏 总结与展望 前言 整个大学期间&#xff0c;我的主…

【论文翻译】结构化状态空间模型

文章目录 3.2 对角结构化状态空间模型3.2.1 S4D:对角SSM算法3.2.2 完整应用实例 3.3 对角化加低秩&#xff08;DPLR&#xff09;参数化3.3.1 DPLR 状态空间核算法3.3.2 S4-DPLR 算法和计算复杂度3.3.3赫尔维兹&#xff08;稳定&#xff09;DPLR形式 这篇文章是Mamba作者博士论文…

LLM量化、高保真图生视频、多模态肢体运动生成、高分辨率图像合成、低光图像/视频增强、相机相对姿态估计

本文首发于公众号&#xff1a;机器感知 LLM量化、高保真图生视频、多模态肢体运动生成、高分辨率图像合成、低光图像/视频增强、相机相对姿态估计 EasyQuant: An Efficient Data-free Quantization Algorithm for LLMs Large language models (LLMs) have proven to be very s…

android实战视频教程,flutter开发实战详解pdf

前言 这是一篇软文、但是绝对不是鸡汤&#xff1b;为啥不是呢&#xff1f;因为我文笔太差…偶尔矫情发发牢骚&#xff08;勿喷&#xff09; 说说程序猿行业 现在社会上给IT行业贴上了几个标签&#xff1a;高薪、高危、高大上、秃顶&#xff08;哈哈&#xff09;。这些标签我…

C++的类与对象(三)

目录 类的6个默认成员函数 构造函数 语法 特性 析构函数 特性 类的6个默认成员函数 问题&#xff1a;一个什么成员都没的类叫做空类&#xff0c;空类中真的什么都没有吗&#xff1f; 基本概念&#xff1a;任何类在什么都不写时&#xff0c;编译器会自动生成以下六个默认…

Linux 性能优化的全景指南,都在这一篇里了,建议收藏!

Linux 性能优化 性能指标 高并发和响应快对应着性能优化的两个核心指标&#xff1a;吞吐和延时 应用负载角度&#xff1a;直接影响了产品终端的用户体验 系统资源角度&#xff1a;资源使用率、饱和度等 性能问题的本质就是系统资源已经到达瓶颈&#xff0c;但请求的处理还…

MySQL下实现纯SQL语句的递归查询

需求 有一个部门表&#xff0c;部门表中有一个字段用于定义它的父部门&#xff1b; 在实际业务中有一个『部门中心』的业务&#xff1b; 比如采购单&#xff0c;我们需要显示本部门及子部门的采购单显示出来。 结构 数据如下&#xff1a; 实现方式如下&#xff1a; WITH RECUR…

内衣洗衣机名牌排行榜前十名:十款强大性能内衣洗衣机精心力荐

小型内衣洗衣机一般是为婴儿宝宝&#xff0c;或者一些有特殊需要的用户而设计使用的&#xff0c;宝宝衣物换洗频繁&#xff0c;而且对卫生方面的除菌要求高&#xff0c;而为避免交叉感染&#xff0c;所以一般不适合和大人的衣物放在一起洗&#xff0c;因此对于有宝宝的家庭来说…

Android多线程实现方式及并发与同步,Android面试题汇总

一. 开发背景 想要成为一名优秀的Android开发&#xff0c;你需要一份完备的知识体系&#xff0c;在这里&#xff0c;让我们一起成长为自己所想的那样。 我们的项目需要开发一款智能硬件。它由 Web 后台发送指令到一款桌面端应用程序&#xff0c;再由桌面程序来控制不同的硬件设…

Plasmo框架开发浏览器插件配置newtab页面,并可以跳转

有关plasmo框架添加页面可以看官方文档&#xff1a;Browser Extension Pages – Plasmo 想要给插件添加一个页面&#xff0c;可以通过添加newtab.tsx添加&#xff1a; 或者通过添加tabs文件夹添加多个页面&#xff1a; 想要访问的话&#xff0c;只需要通过&#xff1a;chrome-…

Python爬虫实战第三例【三】【上】

零.实现目标 爬取视频网站视频 视频网站你们随意&#xff0c;在这里我选择飞某速&#xff08;狗头保命&#xff09;。 例如&#xff0c;作者上半年看过的“铃芽之旅”&#xff0c;突然想看了&#xff0c;但是在正版网站看要VIP&#xff0c;在盗版网站看又太卡了&#xff0c;…

2024年腾讯云轻量16核32G28M服务器优惠价格3468元15个月

2024年腾讯云轻量16核32G28M服务器优惠价格3468元15个月&#xff0c;380GB SSD云硬盘&#xff0c;6000GB月流量。 一张表看懂腾讯云服务器租用优惠价格表&#xff0c;一目了然&#xff0c;腾讯云服务器分为轻量应用服务器和云服务器CVM&#xff0c;CPU内存配置从2核2G、2核4G、…

Linux下du命令和df命令的使用

du命令作用是估计文件系统的磁盘已使用量&#xff0c;常用于查看文件或目录所占磁盘容量。df命令是统计磁盘使用情况&#xff0c;可以用来查看磁盘已被使用多少空间和还剩余多少空间。du命令语法du [选项] [文件或目录名称]参数&#xff1a;-a&#xff1a;--all&#xff0c; 列…

C#,数值计算,求解微分方程的预测校正法(修正欧拉法)算法与源代码

Leonhard Euler 1 微分方程 微分方程&#xff0c;是指含有未知函数及其导数的关系式。解微分方程就是找出未知函数。 微分方程是伴随着微积分学一起发展起来的。微积分学的奠基人Newton和Leibniz的著作中都处理过与微分方程有关的问题。微分方程的应用十分广泛&#xff0c;可…

To 有缘看到的朋友,To myself

To 有缘看到的朋友&#xff0c;To myself 零、00时光宝盒 我们生而为人&#xff0c;而不是什么神仙妖怪&#xff0c;自然逃不脱凡尘种种不易。 世界并不完美&#xff0c;面对很多事情我们都很无奈甚至悲哀&#xff0c;但生活总要继续下去&#xff0c;当困难悄悄地来临&#xff…

【vue3之组合式API】

组合式API 一、setup1.写法2.如何访问3.语法糖4.同步返回对象 二、reactive()和ref()1.reactive()2.ref() 三、computed四、watch函数1侦听单个数据2.侦听多个数据3. immediate4. deep5.精确侦听对象的某个属性 五、生命周期函数六、组件通信1.父传子2. 子传父 七、模版引用1. …

shell脚本一键部署docker

Docker介绍 Docker 是一个开源的平台&#xff0c;用于开发、交付和运行应用程序。它利用容器化技术&#xff0c;可以帮助开发人员更轻松地打包应用程序及其依赖项&#xff0c;并将其部署到任何环境中&#xff0c;无论是开发工作站、数据中心还是云中。以下是 Docker 的一些关键…