【C++哈希表】哈希碰撞,线性探测,二次探测 ,荷载因子,闭散列的实现及string需要特化
创始人
2024-01-20 11:59:55

目录

1.哈希概念

2.哈希碰撞

3.解决哈希冲突

4.哈希表闭散列实现 

        框架: 

         4.3插入


1.哈希概念

  • 线性表以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。线性表查找时间复杂度为O(N),平衡树中为树的高度,即O(logN)搜索的效率取决于搜索过程中元素的比较次数

假如理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。 如果构造一种存储结构,通过某种函数(哈希)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。

  • 插入元素

                根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放

                 计算方法:关键码(数据)%数组的容量的元素个数(除留余数法)

  • 搜索元素

                对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比 较,若关键码相等,则搜索成功

该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表 (Hash Table)(或者称散列表)

插入579 ,查询任意元素的时间复杂度都是O(1)

 2.哈希碰撞

当插入1,11,21,31,41,按计算方法取模的结果都是1 ,不同关键码通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞

  • 把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。

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

  • 如果数据不集中那么会有大量的空间浪费

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

 3.解决哈希冲突

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

3.1闭散列

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

3.1.线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止(实际方法)

            size_t start = hash(kv.first) % _tables.size();size_t i = 0;size_t index = start;// 线性探测while (_tables[index]._status == EXIST){i++;index =start+ i;index %= _tables.size();}

3.2.二次探测 线性探测的缺陷是产生冲突的数据堆积在一块(效率会变低),这与其找下一个空位置有关系,因为找空位置的方式就是挨着往后逐个去找,因此二次探测是为了避免该问题;不在一次一次加了,加二次方,如果算出来的哈希地址大于容量,就取模就好像又走了一遍

size_t start = hash(kv.first) % _tables.size();size_t i = 0;size_t index = start;// 二次探测while (_tables[index]._status == EXIST){i++;index =start + i * i;index %= _tables.size();}

3.3荷载因子 

哈希表什么情况下进行扩容?如何扩容? 

哈希表的荷载因子a:      a=插入的元素/哈希表的容量;这个值a需限制在0.7-0.8之下,达到就扩容;如果不扩容哈希碰撞的可能性就非常大,效率下降。

4.哈希表闭散列实现 

框架: 

    //状态,防止元素被删除后哈希表查询不到一些位置enum Status{EXIST,EMPTY,DELETE,};templatestruct HashData{pair _kv;Status _status = EMPTY;};//仿函数,字符串需要特殊处理templatestruct Hash// 特化template<>struct Hashtemplate>class HashTable{public://接口函数bool Erase(const T& key)HashData* Find(const T& key)bool Insert(const pair& kv)private:vector> _tables;//插入的元素个数size_t _n=0;};

 4.1.查询

  1. 查询逻辑:key先取模得到哈希地址,按照哈希地址找,如果不是那么说明,出现过哈希碰撞,找“下一个”位置,直到找到这个数或者找到“下一个”空位置说明没有这个元素
        HashData* Find(const T& key){if (_tables.size() == 0){return nullptr;}//可以先不管这个仿函数,就是取里面的值Func hash;size_t start = hash(key)% _tables.size();size_t i = 0;size_t index = start;while (_tables[index]._status != EMPTY){if (_tables[index]._kv.first == key && _tables[index]._status == EXIST){return &_tables[index];}i++;index =start + i * i;index %= _tables.size();}return nullptr;}

4.2删除

  1. 删除查询的位置,有就把HashData指针的成员_status改为删除(_DELETE)标记一下,没有就删除失败返回false
bool Erase(const T& key){HashData* ret = Find(key);if (ret == nullptr){return false;}else{--_n;ret->_status = DELETE;return true;}}

 4.3插入

  1. 插入逻辑:先用Find查询是否有这个元素,有就插入失败,再判断是否需要扩容,插入就好;
bool Insert(const pair& kv){HashData* ret = Find(kv.first);if (ret){return false;}// 荷载因子到0.7,就扩容// 荷载因子越小,冲突概率越低,效率越高,空间浪费越多// 荷载因子越大,冲突概率越高,效率越低,空间浪费越少if (_tables.size() == 0 || _n * 10 / _tables.size() >= 7){//扩容size_t newHashSize = _tables.size() == 0 ? 10 : _tables.size() * 2;HashTable newHT;newHT._tables.resize(newHashSize);for (size_t i = 0; i < _tables.size(); i++){if (_tables[i]._status == EXIST){newHT.Insert(_tables[i]._kv);}}_tables.swap(newHT._tables);}Func hash;size_t start = hash(kv.first) % _tables.size();size_t i = 0;size_t index = start;// 线性探测 or 二次探测while (_tables[index]._status == EXIST){i++;index =start + i * i;//index =start + i;index %= _tables.size();}_tables[index]._kv = kv;_tables[index]._status = EXIST;++_n;return true;}

4.3.1.荷载因子为0.7了,扩容会导致容量变大,取模:key/哈希表容量,那么这个哈希表的映射关系不符合了;只能全部重新插入了

 4.3.2字符串需要怎样插入了?

 

4.3.2.1仿函数及string的特化

  • 能转化为整形的就直接转化,string和自定义类型就需要一个特化版本
//仿函数templatestruct Hash{size_t operator()(const K& key){return key;}};// 特化template<>struct Hash{size_t operator()(const string& s){size_t value = 0;for (auto e : s){// BKDR,减少哈希冲突value *= 31;value += e;}return value;}};

 

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
阳澄湖在哪里哪个省的 阳澄湖是... 点击题目下方苏州生活指南有用 有趣 有态度近期的阳澄湖度很高,3月24日,央视二套《第一时间》直播连...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
金属硬度排行 各种金属硬度 世界上最硬的东西排名分别为硫化碳块,石墨烯,金刚石,金属玻璃和金属锇。这五种物体几乎是整个世界上最坚...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...