【1++的数据结构】之哈希(一)

👍作者主页:进击的1++
🤩 专栏链接:【1++的数据结构】


文章目录

  • 一,什么是哈希?
  • 二,哈希冲突
    • 哈希函数
    • 哈希冲突解决
  • unordered_map与unordered_set

一,什么是哈希?

首先我们要知道的是哈希是一种思想----一 一映射。在以前我们讲过的容器中,查找效率最高的就是二叉平衡搜索树,由于其关键码与存储位置之间没有对应的关系,而是通过多次比较关键码的大小来查找,查找的效率取决于比较次数,查找的时间复杂度可以达到O(logN) 。最理想的查找便是不经过任何的比较,直接能够锁定查找值的位置,因此,如果能够构建一种结构,通过某种函数能够使得关键码与存储位置之间一一映射,便能够快速的找到要查找的值。其实,有点类似与通信中的调制与解调哈。
在这里插入图片描述
在这里插入图片描述
如上图:
我们根据插入元素的关键码,将其通过哈希函数的计算后,便可以得到该元素的存储位置。
其是一一映射的关系。
当我们要查找某元素时,我们便可以根据该元素的关键码,再通过哈希函数的计算后,得出该元素的位置。效率是不是感觉高的起飞!!!!!!
我们将该方法称为哈希方法,将转换函数称为哈希函数,将该结构称为哈希表。
这么牛逼的想法,我怎么现在才知道???难道就仅仅这么简单吗???
在这里插入图片描述
哈哈哈哈哈哈!!!当然不会这么容易。一刚才的图为例,当我们插入44,11这样的值后,我们就会发现其产生了冲突,位置被占用了(我们把这种冲突称为哈希冲突)!!!这该怎么办呢? 我们下一节来进行讲解。

二,哈希冲突

我们把不同关键字通过哈希函数计算得出相同的地址的这种现象称为哈希冲突。
既然元素的存储地址是通过哈希函数的计算得出的,那么哈希冲突当然也与哈希函数的设计有关,好的哈希函数,可以减少哈希冲突的发生。

哈希函数

哈希函数的设计原则:

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

常见的几种哈希函数:

直接定址法: 取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B。
其优点是简单,均匀;缺点是要提前知道关键字的分布情况。

除留余数法: 设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址。
那为什么最好要是质数呢?
有以下结论:
如果有一个数列s,间隔为1,那么不管模数为几,都是均匀分布的,因为间隔为1是最小单位

如果一个数列s,间隔为模本身,那么在哈希表中的分布仅占有其中的一列,也就是处处冲突

数列的冲突分布间隔为因子大小,同样的随机数列,因子越多,冲突的可能性就越大。
具体验证过程大家可以看下面这篇文章:
除留余数法为什么选择质数取模

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

折叠法:
折叠法是将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这几部分叠加求和,并按散列表表长,取后几位作为散列地址。
折叠法适合事先不需要知道关键字的分布,适合关键字位数比较多的情况。

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

哈希函数设计的越好,冲突就越少,但冲突无法避免。

哈希冲突解决

最常见的两种解决方法:闭散列与开散列。
我们先来说闭散列:
当发生哈希冲突时,若哈希表未满,则将元素放到下一个空位置。
寻找空位置,也有两种方法:线性探测和二次探测。
什么叫线性探测呢?从冲突的位置开始,一次向后找,知道找到下一个空位置。
哈希表中元素的删除:
在这里插入图片描述
如上图所示,我们采用线性探测法来寻找空位置,当我们要删除元素4时,便会出现一个问题,若我们直接将4从表中物理删除后,当再去查找44时,就会出现找不到的情况。因此我们采用伪删除来解决。就是我们给哈希表中的每个位置给一个标记:EMPTY / FILLED / DELETE。这样就可以避免上面的问题了。
为了使冲突尽可能的少,我们哈希表的空间会被插入的元素个数要大。我们将 插入的元素个数/哈希表的大小 之比称为载荷因子。

enum State{EMPTY,DELETE,FILL};template<class K, class V>struct Hash_Node{pair<K, V> _kv;State _state = EMPTY;};bool Insert(const pair<K, V>& kv){//查找if (Find(kv)){return false;}//扩容if (_size == 0 ||(_size * 10)/ _table.size() >= 7){size_t newsize = _table.size() == 0 ? 5 : _table.size() * 2;Hash_table<K, V> tmp;tmp._table.resize(newsize);for (auto& e : _table){if (e._state == FILL){tmp.Insert(e._kv);}}_table.swap(tmp._table);}Hash _hash;size_t tablei = _hash(kv.first) % _table.size();while (_table[tablei]._state == FILL){++tablei;tablei %= _table.size();}_table[tablei]._kv = kv;_table[tablei]._state = FILL;++_size;return true;}

当超过载荷因子后,要进行扩容,在扩容时我们申请一个容量更大的哈希表,然后将旧表的数据移到新表中,这里我们采用了一个相对较聪明的写法,我们将旧表遍历一遍,调用插入函数进行插入。最后,哈希表的底层是vector,我们再调用vector的交换函数,可以将两个指向不同vector对象的指针进行交换。这样就扩容完成了。

线性探测的缺点:当多个哈希冲突集中在一起,会发生数据堆积,这样在查找时比较的次数就会增多,影响效率。而二次探测就可以避免这样的问题。
二次探测在这里我们就简单了解:
当我们发生冲突时,(设发生冲突的位置为x)我们去找(x+1^2)%表长 这个位置,若还是冲突则找(x-1^2)%表长 这个位置,若仍冲突则找(x+2^2)%表长 这个位置…直到没有冲突。

开散列:
开散列是指:将关键码集合通过哈希函数计算后得出地址,将具有相同地址的关键码集中在一个子集合。每一个子集合称为桶。桶中的元素通过链表链接起来,哈希表中存储的是链表的头结点。
代码如下:

template<class K,class V,class Hash=HashFunc<K>>class Hash_Bucket{typedef Hash_Node<K,V> Node;public:bool Insert(const pair<K, V>& kv){Hash hash;//查找if (Find(kv)){return false;}//扩容if (_size == _Bucket.size())//这里有点小问题:当桶的数量等于元素数量时就扩容,这样冲突小。{if (_size == 0){_Bucket.resize(5);}else{vector<Node*> tmp;tmp.resize(_Bucket.size() * 2);size_t i = 0;for (i = 0; i < _Bucket.size(); i++){Node* cur = _Bucket[i];while (cur){size_t tmpi = hash(cur->_kv.first) % tmp.size();Node* next = cur->_next;cur->_next = tmp[tmpi];tmp[tmpi]=cur;cur = next;}//_Bucket[i] = nullptr;}_Bucket.swap(tmp);}}//插入size_t Bucketi = hash(kv.first) % _Bucket.size();Node* cur = new Node(kv);cur->_next = _Bucket[Bucketi];_Bucket[Bucketi]=cur;_size++;return true;}bool Find(const pair<K, V>& kv){if (_size == 0){return false;}Hash hash;size_t Bucketi = hash(kv.first) % _Bucket.size();Node* cur = _Bucket[Bucketi];while (cur){if (cur->_kv == kv){return true;}else{cur = cur->_next;}}return false;}private:vector<Hash_Node<K, V>*> _Bucket;size_t _size=0;};

这里我们重点讲一下开散列的扩容!!!
首先就是什么时候扩容比较好,开散列性能最好当然就是一个桶直挂一个元素的时候是最好的,因此当桶的数量等于元素的数量时扩容是比较好的。接着我们讲一下扩容的具体操作。与闭散列不同的是,闭散列是直接再实例化了一个哈希表对象,再进行插入操作,最后交换了两个指向vector的指针。而我们的开散列这样做会比较浪费空间,因为我们的元素存储再一个一个的结点中,因此我们不妨将这些结点再利用起来,让其插入再新的哈希表中,再将指向新旧哈希表的指针进行交换。

我们的哈希表只能存储key为整型的元素,那么如何存储其他元素呢?
我们可以提供一个能够将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& key){size_t ret = 0;for (auto e : key){ret += e;}return ret;}};

unordered_map与unordered_set

unordered_map与unordered_set的底层结构为哈希表,其封装过程及其对哈希表的改造与map与set相似这里我们就不过多阐述,我们直接看代码:

改造后的哈希表

template<class T>struct Hash_Node{Hash_Node* _next;T _data;Hash_Node(const T& data):_data(data), _next(nullptr){}};template<class K, class T, class Hash, class KeyOfT>class Hash_Bucket;template<class K,class T,class Hash,class KeyOfT>struct _Iterator{typedef Hash_Node<T> Node;typedef _Iterator Self;typedef Hash_Bucket<K, T, Hash, KeyOfT> _Ht;Node* _node;_Ht* _pht;Hash hash;KeyOfT kot;_Iterator(Node* node,_Ht* pht):_node(node),_pht(pht){}T& operator*(){return _node->_data;}T* operator->(){return &_node->_data;}bool operator!=(const Self& s)const{return _node != s._node;}Self& operator++(){if (_node->_next){_node = _node->_next;}else{size_t i = hash(kot(_node->_data)) % _pht->_Bucket.size();++i;for (; i < _pht->_Bucket.size(); i++){if (_pht->_Bucket[i]){_node = _pht->_Bucket[i];break;}}if (i == _pht->_Bucket.size()){_node = nullptr;}}return *this;}};template<class K,class T,class Hash,class KeyOfT>class Hash_Bucket{typedef Hash_Node<T> Node;template<class K, class T, class Hash, class KeyOfT>friend struct _Iterator;public:typedef typename _Iterator<K, T, Hash, KeyOfT> iterator;inline size_t __stl_next_prime(size_t n){static const size_t __stl_num_primes = 28;static const size_t __stl_prime_list[__stl_num_primes] ={53, 97, 193, 389, 769,1543, 3079, 6151, 12289, 24593,49157, 98317, 196613, 393241, 786433,1572869, 3145739, 6291469, 12582917, 25165843,50331653, 100663319, 201326611, 402653189, 805306457,1610612741, 3221225473, 4294967291};for (size_t i = 0; i < __stl_num_primes; ++i){if (__stl_prime_list[i] > n){return __stl_prime_list[i];}}return -1;}pair<iterator,bool> Insert(const T& data){KeyOfT kot;Hash hash;//查找if (Find(kot(data)).second){return Find(kot(data));}//扩容if (_size ==_Bucket.size()){vector<Node*> tmp;tmp.resize(__stl_next_prime(_Bucket.size()),nullptr);size_t i = 0;for (i = 0; i < _Bucket.size(); i++){Node* cur = _Bucket[i];while (cur){size_t tmpi = hash(kot(cur->_data)) % tmp.size();Node* next = cur->_next;cur->_next = tmp[tmpi];tmp[tmpi]=cur;cur = next;}//_Bucket[i] = nullptr;}_Bucket.swap(tmp);}//插入size_t Bucketi = hash(kot(data)) % _Bucket.size();Node* cur = new Node(data);cur->_next = _Bucket[Bucketi];_Bucket[Bucketi]=cur;_size++;return make_pair(iterator(cur,this),true);}size_t Erase(const K& key){Hash hash;KeyOfT kot;size_t Bucketi = hash(key) % _Bucket.size();Node* cur = _Bucket[Bucketi];Node* prev = nullptr;while (cur){if (kot(cur->_data) == key){//可能为头结点,也可能为中间结点if (kot(_Bucket[Bucketi]->_data) == key){_Bucket[Bucketi]=cur->_next;delete cur;_size--;return 1;}else{prev->_next=cur->_next;delete cur;_size--;return 1;}}else{prev = cur;cur = cur->_next;}}return 0;}pair<iterator,bool> Find(const K& key){KeyOfT kot;if (_size == 0){return make_pair(iterator(nullptr,this),false);}Hash hash;size_t Bucketi = hash(key) % _Bucket.size();Node* cur = _Bucket[Bucketi];while (cur){if (kot(cur->_data) == key){return make_pair(iterator(cur, this), true);}else{cur = cur->_next;}}return make_pair(iterator(nullptr, this), false);}iterator begin(){for (size_t i = 0; i < _Bucket.size(); i++){if (_Bucket[i]){return iterator(_Bucket[i], this);}}return end();}iterator end(){return iterator(nullptr, this);}private:vector<Hash_Node<T>*> _Bucket;size_t _size=0;};

封装的unordered_map

template<class K>struct HashFunc{size_t operator() (const K& key){return (size_t)key;}};template<>struct HashFunc<string>{size_t operator() (const string& key){size_t ret = 0;for (auto e : key){ret += e;}return ret;}};template<class K, class V,class Hash= HashFunc<K>>class unordered_map{struct KeyOfM{const K& operator() (const pair<K, V>& kv){return kv.first;}};public:typedef typename Hash_Bucket<K, pair<K, V>, Hash, KeyOfM>::iterator iterator;pair<iterator,bool> Insert(const pair<K, V>& kv){return _ht.Insert(kv);}size_t Erase(const K& key){return _ht.Erase(key);}V& operator[](const K& key){pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));return ret.first->second;}iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}private:Hash_Bucket<K, pair<K,V>,Hash, KeyOfM> _ht;};

封装的unordered_set

template<class K, class Hash=HashFunc<K>>class unordered_set{ struct KeyOfS{const K& operator() (const K& key){return key;}};public:typedef typename Hash_Bucket<K,K,Hash,KeyOfS>::iterator iterator;pair<iterator, bool> Insert(const K& key){return _ht.Insert(key);}size_t Erase(const K& key){return _ht.Erase(key);}iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}private:Hash_Bucket<K, K, Hash, KeyOfS> _ht;};

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

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

相关文章

[E2E Test] Python Behave Selenium 一文学会自动化测试

前言 本文将使用Python Behave与Selenium&#xff0c;和同学们一起认识自动化测试&#xff0c;并附上完整的实践教程。 项目源码已上传&#xff1a;CSDN 郭麻花 Azure Repo python-behave-selenium 核心概念 1. 什么是E2E Test E2E即End-to-end&#xff0c;意思是从头到尾…

Java ArrayList

简介 ArrayList类示一个可以动态修改的数组&#xff0c;与普通数组的区别是它没有固定大小的限制&#xff0c;可以添加和删除元素。 适用情况&#xff1a; 频繁的访问列表中的某一元素只需要在列表末尾进行添加和删除某些元素 实例 ArrayList 是一个数组队列&#xff0c;提…

外滩大会今日开幕 近20位“两院”院士、诺贝尔奖和图灵奖得主齐聚

2023 Inclusion外滩大会9月7日在上海黄浦世博园正式开幕。这场以“科技创造可持续未来”为主题的大会为期三天&#xff0c;近20位“两院”院士、诺贝尔奖和图灵奖得主&#xff0c;全球超500位有影响力的科技领军企业和专家学者&#xff0c;将在此带来一场科技、人文和产业的思想…

深度学习之视频分类项目小记

写在前面&#xff0c;最近一阵在做视频分类相关的工作&#xff0c;趁有时间来记录一下。本文更注重项目实战与落地&#xff0c;而非重点探讨多模/视频模型结构的魔改 零、背景 目标&#xff1a;通过多模态内容理解技术&#xff0c;构建视频层级分类体系原技术方案&#xff1a…

09-JVM垃圾收集底层算法实现

上一篇&#xff1a;08-JVM垃圾收集器详解 1.三色标记 在并发标记的过程中&#xff0c;因为标记期间应用线程还在继续跑&#xff0c;对象间的引用可能发生变化&#xff0c;多标和漏标的情况就有可能发生。 这里我们引入“三色标记”来给大家解释下&#xff0c;把Gcroots可达性…

golang 通用的 grpc http 基础开发框架

go-moda golang 通用的 grpc http 基础开发框架仓库地址: https://github.com/webws/go-moda仓库一直在更新,欢迎大家吐槽和指点 特性 transport: 集成 http&#xff08;echo、gin&#xff09;和 grpc。tracing: openTelemetry 实现微务链路追踪pprof: 分析性能config: 通用…

【云计算网络安全】解析DDoS攻击:工作原理、识别和防御策略 | 文末送书

文章目录 一、前言二、什么是 DDoS 攻击&#xff1f;三、DDoS 攻击的工作原理四、如何识别 DDoS 攻击五、常见的 DDoS 攻击有哪几类&#xff1f;5.1 应用程序层攻击5.1.1 攻击目标5.1.2 应用程序层攻击示例5.1.3 HTTP 洪水 5.2 协议攻击5.2.1 攻击目标5.2.2 协议攻击示例5.2.3 …

IDEA集成Apipost Helper实现一键部署接口(避免参数注释)

先说好处&#xff1a; 1.一次性导入所有接口&#xff0c;不要一个一个扒。 2.对于字段的注释不要一个一个的去手写&#xff0c;映射实体类&#xff0c;自己上传&#xff08;最重要&#xff09;。 3.目录自动归类划分&#xff0c;避免接口混乱。 安装插件 首先&#xff0c;我们打…

Apache nginx解析漏洞复现

文章目录 空字节漏洞安装环境漏洞复现 背锅解析漏洞安装环境漏洞复现 空字节漏洞 安装环境 将nginx解压后放到c盘根目录下&#xff1a; 运行startup.bat启动环境&#xff1a; 在HTML文件夹下有它的主页文件&#xff1a; 漏洞复现 nginx在遇到后缀名有php的文件时&#xff0c;…

基于springboot实现了后台定时统计数据报表并将数据生成excel文件作为附件,然后通过邮件发送通知的功能

概述 本例子基于springboot实现了后台定时统计数据报表并将数据生成excel文件作为附件&#xff0c;然后通过邮件发送通知的功能。 详细 一、准备工作 1、首先注册两个邮箱&#xff0c;一个发送邮箱&#xff0c;一个接收邮箱。 2、发送邮箱开启IMAP/SMTP/POP3服务&#xff0c…

【嵌入式开发 Linux 常用命令系列 7.1 -- awk 过滤列中含有特定字符的行】

文章目录 awk 过滤列中字符串 上篇文章:嵌入式开发 Linux 常用命令系列 7 – awk 常用方法详细介绍 awk 过滤列中字符串 cat test.log | awk -F $31 {print $0}说明&#xff1a; -F 以什么分隔列&#xff0c;这里是以空格为分隔符&#xff1b;$3代表第3列&#xff1b;$3…

2023 年全国大学生数学建模B题目-多波束测线问题

B题目感觉属于平面几何和立体几何的问题&#xff0c;本质上需要推导几何变换情况&#xff0c;B题目属于有标准答案型&#xff0c;没太大的把握不建议选择&#xff0c;可发挥型不大。 第一问 比较简单&#xff0c;就一个2维平面的问题&#xff0c;但有点没理解&#xff0c;这个…

学习笔记——Java入门第二季

1.1 介绍类与对象 类和对象的关系&#xff1a; 时间万物皆对象。对象是具体的事物&#xff0c;是类的具体事例 类是抽象的概念&#xff0c;是对象的模板。 new关键字是创建实例对象最重要的标志 Dog duoduonew Dog(); Dog luckynew Dog(); 这样就创建了两个对象并且在java内…

尚硅谷大数据项目《在线教育之离线数仓》笔记007

视频地址&#xff1a;尚硅谷大数据项目《在线教育之离线数仓》_哔哩哔哩_bilibili 目录 第12章 报表数据导出 P112 01、创建数据表 02、修改datax的jar包 03、ads_traffic_stats_by_source.json文件 P113 P114 P115 P116 P117 P118 P119 P120 P121 P122【122_在…

LeetCode每日一题:1123. 最深叶节点的最近公共祖先(2023.9.6 C++)

目录 1123. 最深叶节点的最近公共祖先 题目描述&#xff1a; 实现代码与解析&#xff1a; dfs 原理思路&#xff1a; 1123. 最深叶节点的最近公共祖先 题目描述&#xff1a; 给你一个有根节点 root 的二叉树&#xff0c;返回它 最深的叶节点的最近公共祖先 。 回想一下&…

钉钉消息已读、未读咋实现的嘞?

前言 一款app&#xff0c;消息页面有&#xff1a;钱包通知、最近访客等各种通知类别&#xff0c;每个类别可能有新的通知消息&#xff0c;实现已读、未读功能&#xff0c;包括多少个未读&#xff0c;这个是怎么实现的呢&#xff1f;比如用户A访问了用户B的主页&#xff0c;难道…

文字转语音TTS bark SpeechT5 mms

bark GitHub - suno-ai/bark: &#x1f50a; Text-Prompted Generative Audio Model microsoft SpeechT5 https://github.com/microsoft/SpeechT5 使用 SpeechT5 进行语音合成、识别和更多功能 - 掘金 Facebook mms https://github.com/facebookresearch/fairseq/tree/mai…

私有化部署即时通讯平台,完美替代飞书和钉钉的SaaS系统

在当今快速发展的数字化时代&#xff0c;企业对于安全、灵活、可定制的即时通讯平台需求不断增长。作为一家领先的品牌&#xff0c;WorkPlus专注于提供私有化部署的即时通讯平台&#xff0c;完美替代飞书和钉钉的SaaS系统。本文将重点介绍WorkPlus如何通过创新的解决方案&#…

【C刷题训练营】第三讲(c语言入门训练)

前言: 大家好&#xff0c;我决定日后逐渐更新c刷题训练营的内容&#xff0c;或许能帮到入门c语言的初学者&#xff0c;如果文章有错误&#xff0c;非常欢迎你的指正&#xff01; &#x1f4a5;&#x1f388;个人主页:​​​​​​Dream_Chaser&#xff5e; &#x1f388;&…

CSAPP的Lab学习——Archlab(Architecture Lab)

文章目录 前言一、A部分sum .ys&#xff1a;迭代求和链表元素写一个Y86-64的程序和。rsum .递归求和链表元素copy.ys 复制将源块复制到目标块 二、B部分三、C部分实现iaddq指令 总结 前言 一个本硕双非的小菜鸡&#xff0c;备战24年秋招。刚刚看完CSAPP&#xff0c;真是一本神…