哈希表的认识与实现

哈希的概念

可以不经过任何比较,一次直接从表中得到要搜索的元素。如果构造一种存储结构,通过某种函数(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){BKDRsize_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){BKDRsize_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.hqwc.cn/news/215470.html

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

相关文章

深度学习卷积神经网络参数计算难点重点

目录 一、卷积层图像输出尺寸 二、池化层图像输出尺寸 三、全连接层输出尺寸 四、卷积层参数数量 五、全连接层参数数量 六、代码实现与验证 以LeNet5经典模型为例子并且通道数为1 LeNet5网络有7层&#xff1a; ​ 1.第1层&#xff1a;卷积层 ​ 输入&#xff1a;原始的图片像素…

dom api

dom的全称为Document Object Model,即文档对象模型.所谓文档就是html页面,对象就是js里的对象,通过这个模型把页面上的元素和js里的对象关联起来. 下面是关于dom api的一些常用方法 1.获取元素 使用querySelector()方法获取一个元素 使用querySelectorAll()方法获取所有元素 当…

Python基础之中常用的数据类型总结,从入门到入土的python教程之一。

文章目录 Python 中常用的数据类型包括&#xff1a;Python 中布尔类型(bool)Python 中的数字类型概述Pyhon中的字符串概述Python 中的List概述Python 中的元组类型(tuple)Python中的字典&#xff08;Dictionary&#xff09;Python中的集合&#xff08;Set&#xff09;Python中的…

html幸运大转盘抽奖(附源码)

文章目录 1.设计来源1.1 幸运大转盘 风格11.2 幸运大转盘 风格21.3 幸运大转盘 风格31.4 幸运大转盘 奖品效果1.5 幸运大转盘 活动未开始1.6 幸运大转盘 活动已结束1.7 幸运大转盘 图片源素材 2.效果和源码2.1 动态效果2.2 源代码 源码下载 作者&#xff1a;xcLeigh 文章地址&a…

【Web】PhpBypassTrick相关例题wp

目录 ①[NSSCTF 2022 Spring Recruit]babyphp ②[鹤城杯 2021]Middle magic ③[WUSTCTF 2020]朴实无华 ④[SWPUCTF 2022 新生赛]funny_php 明天中期考&#xff0c;先整理些小知识点冷静一下 ①[NSSCTF 2022 Spring Recruit]babyphp payload: a[]1&b1[]1&b2[]2&…

LED驱动控制专用电路

一、基本概述 TM1628是一种带键盘扫描接口的LED&#xff08;发光二极管显示器&#xff09;驱动控制专用IC,内部集成有MCU 数 字接口、数据锁存器、LED 驱动、键盘扫描等电路。本产品质量可靠、稳定性好、抗干扰能力强。 主要适用于家电设备(智能热水器、微波炉、洗衣机、空调…

网络安全—自学

1.网络安全是什么 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 2.网络安全市场 一、是市场需求量高&#xff1b; 二、则是发展相对成熟…

番外篇之矩阵运算

矩阵的运算代码&#xff08;加减乘除&#xff09;&#xff08;内有注释&#xff09; #define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #define ROW 10 //定义行 #define COL 10 //定义列 //设置全局变量A矩阵的m代表实际矩阵的行数&#xff0c;n代表实际矩阵的列…

【数据结构】最小生成树(Kruskal算法)

一.基本思想 设无向连通网为G&#xff08;V&#xff0c;E&#xff09;&#xff0c;令G的最小生成树为T&#xff08;U&#xff0c;TE&#xff09;&#xff0c;其初态为UV&#xff0c;TE{},然后&#xff0c;按照边的权值由小到大的顺序&#xff0c;考察G的边集E中的各条边。若被考…

大数据Doris(二十八):Routine Load查看和修改作业

文章目录 Routine Load查看和修改作业 一、​​​​​​​查看导入作业状态

2023年【熔化焊接与热切割】免费试题及熔化焊接与热切割模拟考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 熔化焊接与热切割免费试题是安全生产模拟考试一点通生成的&#xff0c;熔化焊接与热切割证模拟考试题库是根据熔化焊接与热切割最新版教材汇编出熔化焊接与热切割仿真模拟考试。2023年【熔化焊接与热切割】免费试题及…

用栈实现队列的功能,用队列实现栈的功能?

我们知道队列的特点是先入先出&#xff0c;栈的特点是后入先出&#xff0c;那么如何用栈实现队列的功能&#xff0c;又如何用队列实现栈的功能呢&#xff0c;且听我一一道来 我们首先来看用栈实现队列的功能&#xff0c;首先大伙儿要知道队列和栈的特点其实是“相反”&#xf…