您的位置:首页 > 游戏 > 游戏 > [数据结构] 开散列法 闭散列法 模拟实现哈希结构

[数据结构] 开散列法 闭散列法 模拟实现哈希结构

2024/10/6 6:43:29 来源:https://blog.csdn.net/2301_79465388/article/details/142058843  浏览:    关键词:[数据结构] 开散列法 闭散列法 模拟实现哈希结构

标题:[数据结构] 开散列法 && 闭散列法 模拟实现哈希结构

个人主页:@水墨不写bug



目录

一、闭散列法

核心逻辑的解决

        i、为什么要设置位置状态?(伪删除法的使用)

        ii、哈希函数的设计

接口的实现 

1、插入(Insert)

仿函数取数值域的key

插入的 扩容逻辑

2、删除(Erase)

3、查找(Find)


正文开始:

        在之前的讲解中,我们已经了解到哈希结构的基本实现思路,具体讲解见这一篇:

《[数据结构] 哈希结构的哈希冲突&&解决哈希冲突》。实现哈希的方法主要区别在与哈希冲突的解决不同,我们知道常见的解决哈希冲突的方法有:

        闭散列法:找到下一个空位置存储;

        开散列法(链地址法、开链法):通过一个链表(哈希桶)来解决哈希冲突的问题。

        本文将分别实现上述两种方法,以及其各自的实现思路,主要实现哈希表的主要功能:插入(Insert)、删除(Erase)、查找(Find)等的主要逻辑。


一、闭散列法

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


核心逻辑的解决

        i、为什么要设置位置状态?(伪删除法的使用)

        对于一个位置,如果发生哈希冲突,就需要把数据存放到下一个空位置中去,当我们下一次查找这个数据的时候,就需要走一遍和存储时一样的操作。

        比如,我们要在下面的这样的一个哈希表中存储一个数据:

        14%10 = 4:

        向后探测的条件可以猜测为:

        如果表中的不为空,就向后探测。

        但是当我们删除一个元素:这个元素是探测路径上的一个元素,那么下一次当我们查找14的时候,就找不到了。

        所以,我们在设计哈希数据的时候,就需要在每一个数据结构中设置一个状态值:

//哈希表位置的状态
enum State{EXIST,EMPTY,DELETE};

        这样,我们的状态体系就实现了:哈希表一个位置的默认状态为“EMPTY”;如果插入一个数据,那么这个位置的状态就需要更新为“EXIST”,在删除了一个数据之后,需要将这个位置的状态设置为“DELETE”。

        这样,我们就完美解决了删除数据后导致查找数据无法找到的问题。

        实现闭散列哈希需要一个顺序表,这里用vector即可。每一个数据位置存储的是一个自定义类型哈希数据(HashData),这个结构内部存储:

数据域、状态域

         数据域根据传入的模板参数决定:(比如传入的是int,或者pair<int,int>)

    //哈希表的数据template<class T>struct HashData{//默认构造,一般不会用到HashData():_state(EMPTY),_data(T()){}//插入数据时的构造函数HashData(const T& data):_state(EXIST),_data(data){}T _data;State _state;};

        ii、哈希函数的设计

        对于一般的自定义类型(整形,浮点型,指针类型)可以强制转换为size_t类型,我们可以将默认的哈希函数设计为直接强制类型转换为size_t;

        对于常用自定义类型string, 我们设计如下的哈希类仿函数的特化版本:

//特化string版本
template<>
struct HashFunc<string>
{size_t operator()(const string& s){size_t hash = 0;for (const auto& e : s){hash += e;hash *= 31;}return hash;}
};

接口的实现 

1、插入(Insert)

仿函数取数值域的key

        在插入逻辑之前,我们首先需要知道 插入的数据类型V是不确定的,是通过类模板的参数决定的。(这涉及到了unordered_map和unordered_set的需要复用同一个哈希的封装)

        我们暂时不直接看底层实现,只需要知道有一种仿函数,可以取得数据域的key:

/*模板参数介绍*关键值:K *数值域:V*得到Key的仿函数:KeyOfV *hash值计算仿函数:Hash*/
template<class K,class V,class KeyOfV,class Hash = HashFunc<K>>
class HashTable
{// 具体实现  
};

        由于插入的逻辑比较简单,这里不再解释,直接给出插入的核心逻辑:

bool Insert(const V& data)
{    //仿函数,得到数据的关键值KeyOfV kov;Hash hs;//如果有重复数据,插入失败//Find是通过key来查找的,如果插入的时候哈希表中已经存在,则返回false,插入失败if (Find(kov(data)) != nullptr)return false;//...size_t hashi = hs(kov(data)) % _table.size();while (_table[hashi]._state == EXIST){++hashi;hashi %= _table.size();}_table[hashi]._data = data;_table[hashi]._state = EXIST;_n++;return 0;
}

插入的 扩容逻辑

        《[数据结构] 哈希结构的哈希冲突&&解决哈希冲突》讲解中,我们了解到载荷因子是反应哈希表装载程度的量:用载荷因子和size()的比值来决定什么时候要扩容。

        我们界定:载荷因子*10/size() >= 0.7的时候需要扩容。

        扩容就需要进行数据的转移。

        为了体现复用(实际上是懒),我们可以new一个新的表,再遍历旧表的数据,一个一个插入新表即可,最终交换新表和旧表的vector即可:

//检查是否需要扩容
if (_n * 10 / _table.size() >= 7)
{//新建哈希表,容量为2倍HashTable<K,V,KeyOfV> newHT;newHT._table.resize(_table.size() * 2);//将旧表的数据移动到新表for (int i = 0; i < _table.size(); ++i){if (_table[i]._state == EXIST){newHT.Insert(_table[i]._data);}}_table.swap(newHT._table);
}

插入:

//插入数据
bool Insert(const V& data)
{//仿函数,得到数据的关键值KeyOfV kov;Hash hs;//如果有重复数据,插入失败if (Find(kov(data)) != nullptr)return false;//检查是否需要扩容if (_n * 10 / _table.size() >= 7){//新建哈希表,容量为2倍HashTable<K,V,KeyOfV> newHT;newHT._table.resize(_table.size() * 2);//将旧表的数据移动到新表for (int i = 0; i < _table.size(); ++i){if (_table[i]._state == EXIST){newHT.Insert(_table[i]._data);}}_table.swap(newHT._table);}size_t hashi = hs(kov(data)) % _table.size();while (_table[hashi]._state == EXIST){++hashi;hashi %= _table.size();}_table[hashi]._data = data;_table[hashi]._state = EXIST;_n++;return true;
}

2、删除(Erase)

         删除的逻辑比较简单,我们需要先借助find根据key找到需要删除的数据的应该存在的哈希表的位置,再一个一个向后限行探测即可;如果找到,伪删除,修改状态为DELETE,如果没有找到,返回nullptr:

bool Erase(const K key)
{HashData<V>* ret = Find(key);if (ret == nullptr)return false;else{//修改数据状态表示删除ret->_state = DELETE;return true;}
}

3、查找(Find)

         查找的逻辑也是不复杂的;首先找到数据应该存在的表中的位置,如果一个一个向后限行探测即可;唯一需要注意的是状态为DELETE的时候,需要继续往后查找:

//根据key查找数据
HashData<V>* Find(const K key)
{KeyOfV kov;//得到data的关键值Hash hs;//哈希函数size_t hashi = hs(key) % _table.size();while (_table[hashi]._state != EMPTY){if (_table[hashi]._state == EXIST && kov(_table[hashi]._data) == key)return &_table[hashi];hashi++;hashi %= _table.size();}return nullptr;
}

 待续~

未经作者同意禁止转载

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com