哈希

news/2024/5/16 18:35:50/文章来源:https://blog.csdn.net/qq_61434514/article/details/129009876

一、unordered系列关联式容器

set、map  /  unordered_set、unorder_map 区别:

  • set、map底层结构是红黑树,unordered_set、unorder_map底层结构是哈希表
  • unordered系列是:无序、单向迭代器、效率高( O(1) )

每个容器都自身提供swap成员函数,算法库也有swap,他们的区别是什么?

  •  s1.swap(s2)   ----->  效率高,交换底层结构,比如树:交换根节点指针
  • swap(s1,s2)    ----->  效率低,利用第三个对象,深拷贝交换

 二、底层结构

2.1 哈希概念

unordered系列的关联式容器之所以效率比较高,是因为其底层使用了哈希结构。

理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。
如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立  一一映射的关系,那么在查找时通过该函数可以很快找到该元素
当向该哈希结构中:
1.插入:根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放
2.搜索:对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功
该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(Hash Table)(或者称散列表)

2.2 哈希冲突解决

哈希冲突:不同关键字通过相同哈希哈数计算出相同的哈希地址。

引起哈希冲突的一个原因可能是:哈希函数设计不够合理。
哈希函数设计原则:
  • 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1之间
  • 哈希函数应该比较简单
  • 哈希函数计算出来的地址能均匀分布在整个空间中

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

2.2.1 闭散列

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

1.线性探测 

比如2.1中的场景,现在需要插入元素16,先通过哈希函数计算哈希地址,hashAddr为6,
因此16理论上应该插在该位置,但是该位置已经放了值为6的元素,即发生哈希冲突。
线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止

优点: 实现简单

缺点:我占你的,你占他的,拥堵 (一旦发生哈希冲突,所有的冲突连在一起,容易产生数据堆积”即:不同关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降)

 注意:采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索。因此线性探测采用标记的伪删除法来删除一个元素。

模拟实现: 

namespace Cris
{enum State{EMPTY,EXITS,DELETE};template<class K, class V>struct HashData{pair<K, V> _kv;State _state;};template<class K>struct DefalutHash{size_t operator()(const K& key){return (size_t)key;}};//字符串特化后template<>struct DefalutHash<string>{size_t operator()(const string& key){size_t hash = 0;for (auto ch : key){//131是验证过的随机数hash = hash * 131 + ch;}return hash;}};//HashFunc哈希函数,用来降低哈希冲突的概率,提高利用性//哈希函数设计的越精妙,产生哈希冲突的可能性就越低template<class K, class V, class HashFunc = DefalutHash<K> >class HashTable{typedef HashData<K, V> Data;public:bool Insert(const pair<K, V>& kv){if (Find(kv.first)){return false;}//负载因子到0.7以上就扩容if (_tables.size() == 0 || _n * 10 / _tables.size() >= 7){size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;//扩容之后需要重新映射HashTable<K, V, HashFunc> newHT;for (auto& e : _tables){if (e._state == EXITS){newHT.Insert(e._kv);}}newHT._tables.swap(_tables);}HashFunc hf;//如果有kv.first为字符串,字符串转化为ASCII码值存储size_t starti = hf(kv.first);starti %= _tables.size();size_t hashi = starti;size_t i = 1;//  线性探测/二次探测while (_tables[hashi]._state == EXITS){hashi = starti + i;i++;hashi %= _tables.size();}_tables[hashi]._kv = kv;_tables[hashi]._state = EXITS;_n++;return true;}Data* Find(const K& key){if (_tables.size() == 0){return nullptr;}HashFunc hf;size_t starti = hf(key);starti %= _tables.size();size_t hashi = starti;size_t i = 1;while (_tables[hash]._state != EMPTY){if (_tables[hashi]._state != DELETE && _tables[hashi]._kv.first == key){return &_tables[hashi];}hashi = starti + i;i++;hashi %= _tables.size();}return nullptr;}bool Erase(const K& key){Data* ret = Find(key);if (ret){ret->_state = DELETE;_n--;return true;}else{return false;}}private:vector<HashData> _tables;size_t _n = 0;//存储关键字数};
}

 2.二次探测

 闭散列最大的缺陷就是空间利用率比较低,这也是哈希的缺陷。

2.2.2 开散列 

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

数据不存在表中,表里面存储一个链表指针,冲突的数据链表形式挂起来 

开散列(哈希桶的模拟实现):

template<class K>
struct DefaultHash
{size_t operator()(const K& key){return (size_t)key;}
};// key为字符串类型,需要将其转化为整形
template<>
struct DefaultHash<string>
{size_t operator()(const string& key){size_t hash = 0;for (auto ch : key){hash = hash * 131 + ch;}return hash;}
};namespace Bucket
{template<class T>struct HashNode{T _data;HashNode<T>* _next;HashNode(const T& data):_data(data), _next(nullptr){}};template<class K, class T, class KeyOfT, class HashFunc>class HashTable;template<class K, class T, class KeyOfT, class HashFunc>class __HTIterator{typedef HashNode<T> Node;typedef __HTIterator<K, T, KeyOfT, HashFunc> Self;public:Node* _node;HashTable<K, T, KeyOfT, HashFunc>* _pht;__HTIterator(Node* node, HashTable<K, T, KeyOfT, HashFunc>* pht):_node(node), _pht(pht){}Self& operator++(){if (_node->_next){_node = _node->_next;}else{KeyOfT kot;HashFunc hf;size_t hashi = hf(kot(_node->_data));++hashi;//找下一个不为空的桶for (; hashi < _pht->_tables.size(); ++hashi){if (_pht->_tables[hashi]){_node = _pht->_tables[hashi];break;}}// 没有找到不为空的桶,用nullptr去做end标识if (hashi == _pht->_tables.size()){_node = nullptr;}}return *this;}T& operator*(){return _node->_data;}T* operator->(){return &_node->_data;}bool operator!=(const Self& s) const{return _node != s._node;}bool operator==(const Self& s) const{return _node == s._node;}};// unordered_map ->HashTable<K, pair<K, V>, MapKeyOfT> _ht;// unordered_set ->HashTable<K, K, SetKeyOfT> _ht;template<class K, class T, class KeyOfT, class HashFunc>class HashTable{template<class K, class T, class KeyOfT, class HashFunc>friend class __HTIterator;typedef HashNode<T> Node;public:typedef __HTIterator<K, T, KeyOfT, HashFunc> iterator;iterator begin(){for (size_t i = 0; i < _tables.size(); ++i){Node* cur = _tables[i];if (cur){return iterator(cur, this);}}return end();}iterator end(){return iterator(nullptr, this);}~HashTable(){for (size_t i = 0; i < _tables.size(); ++i){Node* cur = _tables[i];while (cur){Node* next = cur->_next;delete cur;cur = next;}_tables[i] = nullptr;}}//除留余数法,最好模一个素数size_t GetNextPrime(size_t prime){const int PRIMECOUNT = 28;//每次快速取一个类似两倍关系的素数static const size_t primeList[PRIMECOUNT] ={53ul, 97ul, 193ul, 389ul, 769ul,1543ul, 3079ul, 6151ul, 12289ul, 24593ul,49157ul, 98317ul, 196613ul, 393241ul, 786433ul,1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,1610612741ul, 3221225473ul, 4294967291ul};// 获取比prime大那一个素数size_t i = 0;for (; i < PRIMECOUNT; ++i){if (primeList[i] > prime)return primeList[i];}return primeList[i];}pair<iterator, bool> Insert(const T& data){HashFunc hf;KeyOfT kot;iterator pos = Find(kot(data));if (pos != end()){return make_pair(pos, false);}//负载因子 == 1时扩容if (_tables.size() == _n){//防止过多浪费//size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;size_t newSize = GetNextPrime(_tables.size());vector<Node*> newTable;newTable.resize(newSize, nullptr);for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur){Node* next = cur->_next;size_t hashi = hf(kot(cur->_data)) % newSize;//头插cur->_next = newTable[hashi];newTable[hashi] = cur;cur = next;}_tables[i] = nullptr;}newTable.swap(_tables);}size_t hashi = hf(kot(data));hashi %= _tables.size();// 头插到对应的桶即可Node* newnode = new Node(data);newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return make_pair(iterator(newnode, this), false);}Node* Find(const K& key){if (_tables.size() == 0){return nullptr;}KeyOfT kot;HashFunc hf;size_t hashi = hf(key);hashi %= _tables.size();Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == key){return cur;}cur = cur->_next;}return nullptr;}bool Erase(const K& key){if (_tables.size() == 0){return false;}HashFunc hf;KeyOfT kot;size_t hashi = hf(key);hashi %= _tables.size();Node* prev = nullptr;Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == key){if (prev == nullptr){_tables[hashi] = cur->_next;}else{prev->_next = cur->_next;}delete cur;return true;}prev = cur;cur = cur->_next;}return false;}};
}
开散列与闭散列比较
开地址法必须保持大量的空闲空间以确保搜索效率,且表项所占空间又比指针大的多,所以使用链地址法(哈希桶)反而比开地址法节省存储空间。

三、模拟实现

3.1 unorder_map 、unorder_set改造

3.1.1 unorder_map模拟实现

#include "HashTable.h"namespace Cris
{template<class K, class V, class HashFunc = DefaultHash<K>>class unordered_map{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};public:typedef typename Bucket::HashTable<K, pair<K, V>, MapKeyOfT, HashFunc>::iterator iterator;iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}pair<iterator, bool> insert(const pair<K, V>& kv){return _ht.Insert(kv);}iterator find(const K& key){return _ht.Find(key);}bool erase(const K& key){return _ht.Erase(key);}V& operator[](const K& key){pair<iterator, bool> ret = insert(make_pair(key, V()));return ret.first->second;}private:Bucket::HashTable<K, pair<K, V>, MapKeyOfT, HashFunc> _ht;};

3.1.2 unorder_set模拟实现

namespace Cris
{template<class K, class HashFunc = DefaultHash<K>>class unordered_set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename Bucket::HashTable<K, K, SetKeyOfT, HashFunc>::iterator iterator;iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}pair<iterator, bool> insert(const K& key){return _ht.Insert(key);}iterator find(const K& key){return _ht.Find(key);}bool erase(const K& key){return _ht.Erase(key);}private:Bucket::HashTable<K, K, SetKeyOfT, HashFunc> _ht;};

四、哈希的应用

4.1 位图

所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用
来判断某个数据存不存在的。
优点:节省空间、快

4.1.1位图的实现 

namespace Cris
{template<size_t N>class Crisset{public:Crisset(){// +1保证足够比特位,最多浪费8个_bits.resize(N / 8 + 2, 0);}//x映射的位标记成1void set(size_t x){//x映射的比特位在第几个插入对象size_t i = x / 8;//x在插入第几个比特位size_t j = x % 8;_bits[i] |= (1 << j);}//x映射的位标记成0void reset(size_t x){//x映射的比特位在第几个char对象size_t i = x / 8;// x在char第几个比特位size_t j = x % 8;_bits[i] &= (~(1 << j));}//检测第x位是否为1bool test(size_t x){// x映射的比特位在第几个char对象size_t i = x / 8;// x在char第几个比特位size_t j = x % 8;return _bits[i] & (1 << j);}private:std::vector<char> _bits;};
}

4.2 布隆过滤器

布隆过滤器:用多个哈希函数,将一个数据映射到位图结构中
特点:高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”
位图一个key映射一个比特位,存在冲突,既误判。
布隆过滤器一个key映射几个比特位,降低误判的概率,但还是在所难免

 布隆过滤器的实现:

namespace bit
{struct BKDRHash{size_t operator()(const string& s){// BKDRsize_t value = 0;for (auto ch : s){value *= 31;value += ch;}return value;}};struct APHash{size_t operator()(const string& s){size_t hash = 0;for (long i = 0; i < s.size(); i++){if ((i & 1) == 0){hash ^= ((hash << 7) ^ s[i] ^ (hash >> 3));}else{hash ^= (~((hash << 11) ^ s[i] ^ (hash >> 5)));}}return hash;}};struct DJBHash{size_t operator()(const string& s){size_t hash = 5381;for (auto ch : s){hash += (hash << 5) + ch;}return hash;}};struct JSHash{size_t operator()(const string& s){size_t hash = 1315423911;for (auto ch : s){hash ^= ((hash << 5) + ch + (hash >> 2));}return hash;}};template<size_t M,class K = string,class HashFunc1 = BKDRHash,class HashFunc2 = APHash,class HashFunc3 = DJBHash,class HashFunc4 = JSHash>class BloomFilter{public:void Set(const K& key){size_t hash1 = HashFunc1()(key) % M;size_t hash2 = HashFunc2()(key) % M;size_t hash3 = HashFunc3()(key) % M;size_t hash4 = HashFunc4()(key) % M;//cout << hash1 << " " << hash2 << " " << hash3 << endl;_bs.set(hash1);_bs.set(hash2);_bs.set(hash3);_bs.set(hash4);}bool Test(const K& key){size_t hash1 = HashFunc1()(key) % M;if (_bs.test(hash1) == false){return false;}size_t hash2 = HashFunc2()(key) % M;if (_bs.test(hash2) == false){return false;}size_t hash3 = HashFunc3()(key) % M;if (_bs.test(hash3) == false){return false;}size_t hash4 = HashFunc4()(key) % M;if (_bs.test(hash4) == false){return false;}// 可能存在误判//不存在 绝对是真的,存在 可能是假的return true; }//删除的话可能会影响别的值//支持删除的话,布隆过滤器节约空间的特点就没了//void Reset(const K& key);private:bitset<M> _bs;};

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

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

相关文章

用主动游泳的三维水母模型量化美杜莎的(medusan)机械空间的性能(二)(2017)

文章目录用主动游泳的三维水母模型量化美杜莎的&#xff08;medusan&#xff09;机械空间的性能&#xff08;二&#xff09;(2017)原文链接&#xff1a;https://doi.org/10.1017/jfm.2017.3结果3.1 参考案例的游泳动力学3.2 改变钟的主动和被动材料属性3.2.1 改变施加的张力3.2…

virtuoso数据库介绍

在国内&#xff0c;对海量 RDF 数据的管理有着迫切的实际需求&#xff1b; RDF&#xff1a;Resource Description Framework&#xff0c;是一个使用XML语法来表示的资料模型(Data model)&#xff0c;用来描述Web资源的特性&#xff0c;及资源与资源之间的关系。 Virtuoso可以对…

LINUX内核链表

LINUX内核链表 一、传统链表的缺陷 传统的双向循环链表概念简单&#xff0c;操作方便&#xff0c;但存在有致命的缺陷&#xff0c;用一句话来概括就是&#xff1a; 每一条链表都是特殊的&#xff0c;不具有通用性。换句话说&#xff0c;对于每一种不同的数据&#xff0c;所构…

VMware安装Linux虚拟机后忘记root密码处理方法

OS版本&#xff1a;Red Hat 7.7 问题说明&#xff1a; 之前用VMWare安装了一台Linux虚机&#xff0c;由于长期没使用&#xff0c;导致忘记了root密码。所以需要修改root密码。 Root密码修改 现将修改root密码的操作步骤记录如下。 1.启动虚拟机&#xff0c;出现启动倒计时…

Git 基本操作之Git GUI界面和git命令行如何选择

1. 为啥推荐使用git命令行 我发现公司有很多的同事都喜欢使用git的GUI界面工具&#xff0c;喜欢鼠标点点点就完成了代码的提交&#xff0c;这种方式的确是比较简单便捷&#xff0c;但是却存在风险。先上一个事故给大家醒醒脑。 VScode Git 界面操作引发的惨案 上面的惨案是VS…

一种基于加密域的数字图像水印算法的设计与实现(附Matlab源码)

一种基于加密域的数字图像水印算法的设计与实现 项目介绍 毕设项目 题目&#xff1a;一种基于加密域的数字图像水印算法的设计与实现 随着数字媒体技术的发展&#xff0c;数字媒体版权的保护得到了越来越多人的重视&#xff0c;数字水印技术作为数字媒体版权保护的有效手段…

JavaScript 教程导读

JavaScript 是 Web 的编程语言。所有现代的 HTML 页面都使用 JavaScript&#xff0c;可以用于改进设计、验证表单、检测浏览器、创建cookies等。JavaScript 非常容易学。本教程将教你学习从初级到高级JavaScript知识。JavaScript 在线实例本教程包含了大量的 JavaScript 实例&a…

如何用P6软件编制项目进度计划(下)

卷首语 根据项目合同包含的工作范围进行工作分解&#xff08;WBS&#xff09;&#xff0c;按照业主的要求及项目管理的需要&#xff0c;考虑不同阶段和层次&#xff0c;适时编制出项目管理所要求的的各级进度计划。 4搜集项目计划与进度控制相关信息 搜集与项目计划编制与进…

计算机SCI期刊审稿人,一般关注论文的那些问题? - 易智编译EaseEditing

编辑主要关心&#xff1a; &#xff08;1&#xff09;文章内容是否具有足够的创新性&#xff1f; &#xff08;2&#xff09;文章主题是否符合期刊的受众读者&#xff1f; &#xff08;3&#xff09;文章方法学是否合理&#xff0c;数据处理是否充分&#xff1f; &#xff08;…

谷歌seo快速排名优化方法?谷歌seo排名技巧

本文主要分享关于谷歌seo排名如何快速提升的一些技巧。 本文由光算创作&#xff0c;有可能会被修改和剽窃&#xff0c;我们佛系对待这种行为吧。 谷歌seo快速排名优化方法&#xff1f;谷歌seo排名有什么技巧&#xff1f; 答案是&#xff1a;持续建设GPB外链可有效提升谷歌排…

TCP的拥塞控制算法之一:慢启动算法、拥塞避免算法

目录 什么是拥塞控制&#xff0c;为什么需要拥塞控制 慢启动 拥塞避免 什么是拥塞控制&#xff0c;为什么需要拥塞控制 拥塞通常是指从随着网络中的主机增加其发送速率并因为网络的原因使网络变得十分拥挤&#xff0c;此时会经常发生丢包现象&#xff0c;导致网络的传输效率…

Spring Boot集成Quartz实现定时任务的动态创建、启动、暂停、恢复、删除

一、整个 Quartz 的代码流程基本基本如下&#xff1a;首先需要创建我们的任务(Job)&#xff0c;比如取消订单、定时发送短信邮件之类的&#xff0c;这是我们的任务主体&#xff0c;也是写业务逻辑的地方。创建任务调度器(Scheduler)&#xff0c;这是用来调度任务的,主要用于启动…

【数据结构与算法】二分查找 移除元素

今日任务 数组理论基础 704.二分查找 27.移除元素 1.数组理论基础 &#xff08;1&#xff09;数组是存放在连续内存空间上的相同类型数据的集合。 注意&#xff1a; 数组下标都是从0开始的数组内存空间的地址是连续的 &#xff08;2&#xff09;正因为数组在内存空间的…

(考研湖科大教书匠计算机网络)第四章网络层-第四节:IP数据报的发送和转发过程

获取pdf&#xff1a;密码7281专栏目录首页&#xff1a;【专栏必读】考研湖科大教书匠计算机网络笔记导航 文章目录一&#xff1a;概述二&#xff1a;举例三&#xff1a;路由器可以隔离广播域本节对应视频如下 【计算机网络微课堂&#xff08;有字幕无背景音乐版&#xff09;】&…

记一次OOM

1,问题描述&#xff1a; 新上了一版代码之后&#xff0c;上游服务请求我们服务失败&#xff0c;报错&#xff1a;“服务不可用”&#xff0c;发现注册中心上服务掉线&#xff0c;查询日志&#xff1a;发现oom&#xff1a;Java heap space,GC overhead limit exceeded。 容易…

【R语言(二):Nomogram(诺莫图/列线图)绘制 / R语言逻辑回归分析】

R语言(二)&#xff1a;Nomogram(诺莫图/列线图)绘制 1、基本概念 Nomogram&#xff0c;中文常称为诺莫图或者列线图。简单的说是将Logistic回归或Cox回归的结果进行可视化呈现。它根据所有自变量回归系数的大小来制定评分标准&#xff0c;给每个自变量的每个取值水平一个评分&…

Mysql使用规范(纯技术和实战建议)

1、事务隔级别: &#xff08;强制&#xff09;&#xff1a;Repeatable-Read&#xff08;重复读&#xff09;&#xff0c;且不能在会话操作时临时开启隔离级别。 注&#xff1a; Repeatable-Read&#xff08;重复读&#xff09;隔离级别解决不了幻读。 可用 show variables l…

内存泄漏检测组件 -- hook

目录 hook malloc与free出现的问题 builtin_return_address(N) C/CLinux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂 hook malloc与free出现的问题 #define _GNU_SOURCE #include <stdio.h> #include <dlfcn.h> #include <stdlib.h> /****…

上采样学习

最近邻 简单来说就是x方向和y方向分别复制 #!/usr/bin/env python # _*_ coding:utf-8 _*_ import numpy as np import torch from cv2 import cv2 from torch import nndef numpy2tensor(x: np.ndarray) -> torch.Tensor:"""(H,W) -> (1, 1, H, W)(H,W…

迁移案例实操:MySQL迁移到DM8由于有248张表存在datetime字段类型,使用dts迁移到达梦报不支持数据类型【附数据对比工具】

本文主要记录MySQL数据迁移到DM8上遇到MySQL源端表存在datetime数据类型时&#xff0c;并且包含datetime数据类型的表达上百张的的情况下&#xff0c;如何完成数据迁移的完整步骤。 1. 解决方法 将MySQL源端表的是datetime数据类型的字段修改为varchar(30)。 2. 处理步骤 &a…