C++开散列(哈希桶)的模拟实现
文章目录
1.引言
哈希,就是一种映射关系,当我们把相同的值映射到相同容器的下标上时,这种容器就叫哈希表。不同的哈希表的不同映射规则就又组成了不同的哈希表。上述这种直接映射这种也是最简单的映射规则。
但若出现范围差距过大,如1,2,1000,2的情况,不可能开一个大小为1000的容器,所以想到了除留余数法。下标放数据大小模上容器大小的余数即可。
但这样不同值如12,2等又可能映射到同一个位置上,这种状况又称哈希冲突。
为了解决哈希冲突,我们才引入哈希冲突的解决方案之一:哈希桶。
C++哈希开散列,又称哈希桶,是为了解决哈希冲突而诞生的方法。它的核心目的不是避免冲突,哈希冲突或多或少都是不可避免的,而是尽量减少冲突。
而哈希桶的特色是什么呢?在哈希桶中,会冲突的值采用单链表节点的方式链接,只把头指针放入哈希表中即可。
2.实现说明
在本次模拟实现哈希桶中,我们会具体实现:哈希节点HashNode、哈希桶类HashBucket、以及里面的**插入Insert()**函数、**删除Erase()**函数、**查找Find()**函数、以及测试函数等。
2.1哈希节点HashNode
哈希节点本事要用pair对存放kv数据,然后又需要实现单链表的结构,所以又需要一个_next指针,只需这二者即可。
template<class K,class V>
struct HashNode
{
HashNode<K, V>* _next; //_next下一个指针
pair<K, V> _kv; //存放kv数据的pair对
HashNode(const pair<K,V>& kv) //初始化构造函数
:_next(nullptr)
,_kv(kv)
{}
};
2.2哈希桶类HashBucket
哈希桶类的基本结构如下:
template<class K,class V>
class HashBucket
{
typedef HashNode<K, V> Node;
public:
Node* Find(const K& key) //Find查找函数
{}
bool Erase(const K& key) //Erase删除函数
{}
bool Insert(const pair<K, V>& kv) //Insert插入函数
{}
private:
vector<Node*> _tables; // 指针数组
size_t _n = 0; //有效数据个数
};
2.3Insert()插入函数
插入数据之前一般要用**负载因子(有效数据个数/容器大小)**判断是否要扩容,一般的如闭散列的负载因子控制在0.7-0.8左右,但是这个哈希桶由于其链式结构特殊,只需等到数据放满(负载因子 == 1)再扩容即可。
需要扩容的话,需要新开个新表代替,而且映射关系要重新建立。
若不需要扩容,则采用头插即可。
bool Insert(const pair<K, V>& kv)
{
if (Find(kv.first))
{
return false;
}
//负载因子等于1才扩容 哈希桶的话
if (_n == _tables.size()) //扩容的话需要新开一个vector数组,还需要重新映射对应关系
{
size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
vector<Node*> newtables(newsize,nullptr);
for (Node*& cur : _tables)
{
while (cur)
{
Node* next = cur->_next;
size_t hashi = cur->_kv.first % newtables.size();
//头插到新表
cur->_next = newtables[hashi];
newtables[hashi] = cur;
cur = next;
}
}
_tables.swap(newtables);
}
size_t hashi = kv.first % _tables.size(); //要插入数据对应的hash下标
//头插
Node* newnode = new Node(kv);
newnode->_next = _tables[hashi];
_tables[hashi] = newnode;
++_n;
return true;
}
2.4Erase()删除函数
删除节点的话需要分为两种情况:
1.删除的是头节点,那么删除前就需要保存下来前一个,否则会丢失。
2.删除的是头节点以下的节点,那么按正常逻辑删除即可。
bool Erase(const K& key)
{
size_t hashi = key % _tables.size();
Node* prev = nullptr;
Node* cur = _tables[hashi];
while (cur)
{
if (cur->_kv.first == key)//找到了开始删除
{
if (prev == nullptr)//删除的是头节点
{
_tables[hashi] = cur->_next; //删除前需要保存
}
else//删除的是下面节点
{
prev->_next = cur->_next;
}
delete cur;
return true;
}
else
{
prev = cur;
cur = cur->_next;
}
}
return false;
}
2.5Find()查找函数
定义一个当前节点cur往下正常走即可,走之前需要判断size是否为0
Node* Find(const K& key)
{
if (_tables.size() == 0)
return nullptr;
size_t hashi = key % _tables.size();
Node* cur = _tables[hashi];
while (cur)
{
if (cur->_kv.first == key) //找到了正常返回
{
return cur;
}
cur = cur->_next; //没找到往下走
}
return nullptr; //走完了没找到就是没有
}
2.6测试
采用以下代码测试
void test_HashBucket()
{
int a[] = {3,33,2,13,5,12,1002};
HashBucket<int, int> ht;
for (auto e : a)
{
ht.Insert(make_pair(e,e));
}
cout<<ht.Find(13);
}
结果测试成功!