分享

UC头条:C :哈希:闭散列哈希表

 cnzrp 2023-06-09 发布于山西

哈希的概念

哈希表就是通过哈希映射,让key值与存储位置建立关联。比如,一堆整型{3,5,7,8,2,4}在哈希表的存储位置如图所示:

点击加载图片

插入数据的操作:

在插入数据的时候,计算数据相应的位置并进行插入。

查找数据的操作:

计算key值所在的位置,并判断该位置的值是否等于key,如果等于查找成功。

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

为哈希表(HashTable)(或者称散列表)

哈希冲突

所谓哈希冲突,就是前后插入的key值通过计算,得到的存储位置的地址是相同的,这种现象就是哈希冲突,也称为哈希碰撞。可以把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。比如在上面的图中,可以看到2和4都为哈希冲突现象。

哈希函数

引起哈希冲突的原因之一可能是哈希函数的设计不合理,即计算存储地址的算法出现了不合理。

哈希函数设计原则:

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

常用的哈希函数:

(1)直接定址法:取关键字的某个线性函数为散列地址:Hash(Key)=A*Key+B。其优点是简单切数据分布均匀。其缺点是需要事先知道关键字的分布情况,因此直接定址法适用于数据小且连续的情况。

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

闭散列

为了解决哈希冲突,有闭散列和开散列两种常见方法。接下来先介绍闭散列。

闭散列也叫做开放定址法,当哈希冲突的时候,如果哈希表没有被装满,说明哈希表中有其它位置,那么就把key值存放到冲突位置的下一个空位置上。(这里的下一个位置,并不是说真正的下一个位置,而是往后找,找到一个空位置)。

线性探测

线性探测就是:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。

插入步骤:(1)通过哈希函数获取待插入元素在哈希表中的位置。(2)如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,使用线性探测找到下一个空位置,插入新元素。

点击加载图片

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

闭散列哈希表的简单代码实现:

定义哈希表存储的节点,使用状态来表示闭散列中元素的删除或空位置。

//定义状态。用于插入删除操作enumState{EMPTY,EXIST,DELETE,};//每一个数据的节点templatestructHashData{pair_kv;State_state=EMPTY;};

插入操作:

插入操作的思路是拿着需要插入的数据进行取模,取模得到初步确认的下标。然后从这个下标开始寻找存储状态为EMPTY空的位置,然后插入数据。

负载因子:闭散列哈希表最好不能满,即留出一些空位置。因此我们通过负载因子来判断是否需要扩容。当负责因子大于等于0.7,即哈希表的位置已经使用了百分之七十的时候,就扩容。负责因子的计算方法是哈希表中有效数据个数/哈希表的大小。

扩容的方法:创建一个新的哈希对象,然后遍历旧的哈希表,根据旧的哈希表的数据来重新计算数据的位置。在新表插入数据的操作就是使用这个新的哈希对象调用insert函数即可。

boolInsert(constpair&kv){//如果存在了就直接返回false;if(Find(kv.first))returnfalse;//负载因子如果大于0.7,则扩容if(_n*10/_tables.size>=7){HashTablenewHt;//扩容原来的两倍newHt._tables.resize(_tables.size*2);//这一步是按照旧表中的数据插入到新表中for(auto&e:_tables){//如果旧表中的数据存在,状态为EXIST,//那么让新表调用Insert函数,这不是递归哦!if(e._state==EXIST){newHt.Insert(e._kv);}}//最后,让原本在vector中的旧表,与新表交换。_tables.swap(newHt._tables);}//不需要扩容Hashhf;//因为是泛型,不知道使用的类型是int还是char还是string//因此,需要获取该类型变量的值的整型,再去模size;size_thashi=hf(kv.first)%_tables.size;while(_tables[hashi]._state==EXIST){//线性探测++hashi;hashi%=_tables.size;}_tables[hashi]._kv=kv;_tables[hashi]._state=EXIST;++_n;returntrue;}

删除操作:

由于直接将哈希表中的数据删除,会影响后续的其它操作,因此对于闭散列哈希表使用伪善处。把要删除的数据的状态置为DELETE即可。

boolErase(constK&key){Data*ret=Find(key);if(ret){ret->_state=DELETE;--_n;returntrue;}else{returnfalse;}}

查找操作:

若要查找key值的话,先计算出下标,然后从这个位置开始遍历查找,当这个位置上的数据与key值相同并且其状态为EXIT,那么就返回地址。如果找不到返空指针。

Data*Find(constK&key){Hashhf;size_thashi=hf(key)%_tables.size;while(_tables[hashi]._state!=EMPTY){if((_tables[hashi]._state==EXIST)&&(_tables[hashi]._kv.first==key)){return&_tables[hashi];}++hashi;hashi%=_tables.size;}returnnullptr;}

由于哈希表的数据类型是泛型,我们不知道要传入的数据类型是int还是string还是什么类型的,因此闭散列的难点之一是取模。因此我们要将key转化成整型,然后去取模。

如果原本就是整型,那么就直接返回这个值。如果是string类,那么就逐个将单个字符取出并累加起来,转为size_t类型做返回值。每获取一个字符,将其*31。因为对于字符串来说,冲突的可能很大,乘31减少冲突性。

代码如下:

templatestructHashFunc{size_toperator(constK&key){return(size_t)key;}};//特化template<>structHashFunc{size_toperator(conststring&key){size_thash=0;for(autoch:key){hash*=31;hash+=ch;}returnhash;}};

整体代码如下:

#pragmaonce#include#include#includeusingnamespacestd;templatestructHashFunc{size_toperator(constK&key){return(size_t)key;}};//特化template<>structHashFunc{size_toperator(conststring&key){size_thash=0;for(autoch:key){hash*=31;hash+=ch;}returnhash;}};namespaceclosehash{//定义状态。用于插入删除操作enumState{EMPTY,EXIST,DELETE,};//每一个数据的节点templatestructHashData{pair_kv;State_state=EMPTY;};template<>>classHashTable{typedefHashDataData;public://初始化HashTable:_n(0){_tables.resize(10);}boolInsert(constpair&kv){//如果存在了就直接返回false;if(Find(kv.first))returnfalse;//负载因子如果大于0.7,则扩容if(_n*10/_tables.size>=7){HashTablenewHt;//扩容原来的两倍newHt._tables.resize(_tables.size*2);//这一步是按照旧表中的数据插入到新表中for(auto&e:_tables){//如果旧表中的数据存在,状态为EXIST,//那么让新表调用Insert函数,这不是递归哦!if(e._state==EXIST){newHt.Insert(e._kv);}}//最后,让原本在vector中的旧表,与新表交换。_tables.swap(newHt._tables);}//不需要扩容Hashhf;//因为是泛型,不知道使用的类型是int还是char还是string//因此,需要获取该类型变量的值的整型,再去模size;size_thashi=hf(kv.first)%_tables.size;while(_tables[hashi]._state==EXIST){//线性探测++hashi;hashi%=_tables.size;}_tables[hashi]._kv=kv;_tables[hashi]._state=EXIST;++_n;returntrue;}Data*Find(constK&key){Hashhf;size_thashi=hf(key)%_tables.size;while(_tables[hashi]._state!=EMPTY){if((_tables[hashi]._state==EXIST)&&(_tables[hashi]._kv.first==key)){return&_tables[hashi];}++hashi;hashi%=_tables.size;}returnnullptr;}boolErase(constK&key){Data*ret=Find(key);if(ret){ret->_state=DELETE;--_n;returntrue;}else{returnfalse;}}private:vector_tables;//将每个数据放到vector中size_t_n=0;//哈希表中存储的有效数据的个数};}

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多