开散列(链地址法)
- 原理:
- 本质是链表集合(vector + list),将发生冲突的元素挂在同一个链表中。哈希表中没有直接存储元素,而存储的是链表首节点的地址
- 映射关系:通过对元素 关键字(key) 取模数组容量,计算哈希地址
- 哈希冲突:将产生冲突的节点挂载到同一链表中
- 相较于闭散列:需要额外的指针域空间,但是不会影响别的节点查找和插入,整体插入、查找效率较高
哈希节点(链表):
// 哈希节点(单链表)
template <class T>
struct HashNode
{
T _value;
HashNode<T>* _next;
HashNode(const T& value = T())
:_value(value)
, _next(nullptr)
{}
};
- 哈希函数:除留余数法:
key % _table.size()
- 解决哈希冲突:将冲突元素挂载到同一链表中
- 扩容时机:当负载因子大于 1 时,扩容至 2 倍,并将原始数组中的节点插入新数组
- 负载因子:有效元素个数 / 数组容量
- 扩容方法:
- 创建新的数组,并将旧表中的哈希节点插入到新表中,没有必要重新创建节点
- 交换两个变量,因此扩容函数结束时会释放临时变量
- 或者提供素数表:每次都扩容至素数大小,原因:素数只能被 1 和 本身整除,因此造成哈希冲突的可能性就降低了
- 删除一个节点:
- 通过哈希函数计算哈希地址,然后完成单链表删除工作
#include <iostream>
#include <vector>
using namespace std;
template <class K>
struct KofV
{
const K& operator()(const K& key)
{
return key;
}
};
// 哈希表
template <class K, class V, class KofV>
class HashTable
{
typedef HashNode<V> Node;
public:
HashTable(size_t size = 5)
:_table(size)
, _size(0)
{}
bool Insert(const V& value)
{
// 检查容量
checkCapacity();
// 计算位置
KofV kov;
int index = kov(value) % _table.size();
// 查重
Node* cur = _table[index];
while (cur)
{
if (kov(cur->_value) == value)
return false;
cur = cur->_next;
}
// 进行头插
cur = new Node(value);
cur->_next = _table[index];
_table[index] = cur;
++_size;
return true;
}
void checkCapacity()
{
// 负载因子 = 有效元素个数 / 数组容量 = 1时需要扩容
if (_size == _table.size())
{
size_t newSize = 2 * _size;
vector<Node*> newHt(newSize);
KofV kov;
// 遍历原数组,将所有节点插入新数组
for (size_t i = 0; i < _table.size(); ++i)
{
Node* cur = _table[i];
while (cur)
{
// 保存旧表下一个元素
Node* next = cur->_next;
// 重新计算新表位置,并将其插入新表
int index = kov(cur->_value) % newHt.size();
cur->_next = newHt[index];
newHt[index] = cur;
cur = next;
}
_table[i] = nullptr;
}
_table.swap(newHt);
}
}
Node* find(const K& key)
{
if (_table.size() == 0)
return nullptr;
int index = key % _table.size();
Node* cur = _table[index];
KofV kov;
while (cur)
{
if (kov(cur->_value) == key)
return cur;
cur = cur->_next;
}
return nullptr;
}
bool erase(const K& key)
{
int index = key % _table.size();
Node* cur = _table[index];
Node* prev = nullptr;
KofV kov;
while (cur)
{
if (kov(cur->_value) == key)
{
if (prev == nullptr)
_table[index] = cur->_next;
else
{
prev->_next = cur->_next;
}
delete cur;
--_size;
return true;
}
prev = cur;
cur = cur->_next;
}
return false;
}
private:
vector<Node*> _table;
size_t _size;
};