初识HashTable
HashTable:散列表/哈希表。根据关键字key而直接进行访问数据结构;即哈希表是通过吧关键字值映射到表中的一个位置来访问记录
这个映射函数叫做散列函数,存放记录的数组叫做散列表/哈希表
若结构中存在关键字和K相等的记录,则必定存储在f(K)的位置上。因此,不需要比较便可以直接取得所要查找的记录。这个对应关系 f 称为散列函数,按照这个思想建立的表为散列表
构造散列表/哈希表的几种常用方法:
- 直接定址法:取关键字的某个线性函数为散列地址,Hash(key)= key 或 Hash(key)= A*key + B,A、B为常数
- 除留余数法:取关键值被某个不大于散列表长m的数p除后的所得的余数为散列地址。Hash(key)= key % p
- 平方取中法
- 折叠法
- 随机数法
- 数学分析法
哈希冲突
- 不同的key值经过哈希函数Hash(key)处理以后可能产生相同的值哈希地址,我们称这种情况为哈希冲突
- 任意的散列函数都不能避免产生冲突。
处理哈希冲突的方法
闭散列方法/开放定址法
- 开放定制法:指的是可存放新表项的空闲地址既可以向它的同义词表项开放,又可以向它的非同义词表项开放,其递推公式为:Hi=(H(key)+di)%m;其中m表示散列表表长,di为增量序列
线性探测
- 线性探测法:当发生冲突时,若该索引对应的存储位置已有数据,则以线性的方式往后寻找空的存储位置此一来若后面的位置已被填满而前面还有位置时,可将数据放到前面
- 假设给出一组元素,它们的关键值为:37、25、14、36、49、68、57、11.散列表为H[12],表的大小m=12,则di=1,2,3,……,则有Hi=(H(key)+di)%12:
- 可以看到,线性探测法可能使第i个散列地址的同义词存入第i+1个散列地址,这样本应该存入第i+1个散列地址的元素就争夺第i+2个散列地址的元素的地址……,从而造成大量元素在相邻的散列地址上”聚集”起来,大大降低了查找效率
线性探测算法实现:
底层:使用vector容器来实现
哈希函数的构造:整数我们可以直接采用 key%_tables.size() 来实现,但是为了让我们的哈希表不局限与类型,我们采用仿函数来实现,对于非整数的key,我们通过仿函数将其处理成整数,然后统一进行 key%_tables.size() 操作
增容:我们在对哈希表进行插入或者删除操作的时候,若遇到表为空或者已满,则需要对哈希表进行增容操作。同时为了减少哈希冲突,有以下两个建议
- 哈希表的占用率(填入表中的元素个数/散列表的长度),也称载荷因子a,不能超过1,对于开放定址法,我们一般限制a在0.7-0.8以下
- 哈希表的长度最好为素数(因子少),这样不太容易产生哈希冲突
- 哈希表的增容代价是非常大的,它不像vector底层一样,复制再删除,因为哈希表需要再次进行映射;所以,我们对哈希表增容采用重新构造哈希表的方法,重新进行映射。这就意味着我们需要重新开辟空间,我们使用的是resize()而不是reserve(),这是因为容器调用resize()函数后,所有的空间都已经初始化了,所以可以直接访问。而reserve()函数预分配出的空间没有被初始化,所以不可访问
插入:定义一个枚举类型来记录每一个位置的状态(存在,空,删除),插入过程中,如果表为空或者为满,则我们需要进行增容操作。若多个key值相等,则只执行一个插入操作;若当前映射位置状态为EMPTY或者DELETE,则可以直接进行插入;若当前映射位置状态为EXIT,则需要继续向后找可以插入的位置,若一直找到表尾还没有找到,则从头继续找
删除:我们采用为删除法,即只将要删除的元素当前的位置制为DELETE
查找:若key值所在位置状态为EMPTY则停止查找,返回NULL,判断key值如果存在并且当前位置状态为存在,则找到;状态不是存在则没有找到,没有找到则向后移动,最后判断是否到表尾,若到表尾则继续从头查找
C++代码实现:
#include <iostream>
#include <vector>
using namespace std;
enum State
{
EMPTY = 1,
EXIT = 2,
DELETE = 3,
};
template <class K,class V>
struct HashNode
{
K _key;
V _value;
State _state;
HashNode()
:_state(EMPTY)
{}
};
template <class K>
struct HashFunc
{
size_t operator()(const K& key)
{
return key;
}
};
template <class K, class V, class HashFunc = _HashFunc<K> >
class HashTable
{
typedef struct HashNode<K,V> Node;
public:
HashTable()
:_size(0)
{
_table.resize(_GetPrime(0));
}
HashTable(const HashTable<K, V, HashFunc>& hash)
{
int newSize = hash._table.size();
_table.size(newSize);
for (size_t i = 0; i < _table.size(); ++i)
{
if (hash._table[i]._state == EXIT)
{
_table[i]._key = hash._table[i]._key;
_table[i]._value = hash._table[i]._value;
_table[i]._state = EXIT;
}
}
}
bool Insert(const K& key, const V& value)
{
_CheckCapacity();
size_t index = _HashFunc(key);
if (index >= 0)
{
while (_table[index]._state == EXIT)
{
if (_table[index]._key == key)
return false;
++index;
if (index == _table.size())
index = 0;
}
_table[index]._key = key;
_table[index]._value = value;
_table[index]._state = EXIT;
++_size;
return true;
}
return false;
}
Node* Find(const K& key)
{
size_t index = _HashFunc(key);
if (index >= 0)
{
while (_table[index]._state != EMPTY)
{
if (_table[index]._key == key)
return &_table[index];
++index;
if (index == _table.size())
index = 0;
}
}
return NULL;
}
bool Erase(const K& key)
{
struct HashNode<K,V>* ret = Find(key);
if (ret)
{
ret->_state = DELETE;
}
else
return false;
}
protected:
int _HashFunc(const K& key)
{
if (_table.size())
{
return HashFunc()(key) % _table.size();
}
return -1;
}
void _CheckCapacity()
{
if (_size * 10 / _table.size() >= 7 || _table.size() == 0)
{
size_t newSize = _GetPrime(_table.size());
HashTable<K, V, HashFunc> newTable;
newTable._table.resize(newSize);
for (size_t i = 0; i < _table.size(); i++)
{
newTable.Insert(_table[i]._key, _table[i]._value);
}
_table.swap(newTable._table);
}
}
size_t _GetPrime(const size_t size)
{
const int _PrimeSize = 28;
static const unsigned long _PrimeList[_PrimeSize] =
{
53ul, 97ul, 193ul, 389ul, 769ul,
1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
1610612741ul, 3221225473ul, 4294967291ul
};
//以下查找比当前数还要大的素数
for (size_t i = 0; i <_PrimeSize; i++)
{
if (size < _PrimeList[i])
return _PrimeList[i];
}
//没找到就返回最后一个数
return _PrimeList[_PrimeSize - 1];
}
private:
vector<Node> _table;
size_t _size;
};
- 插入运行结果如下所示:
删除运行结果如下所示:
字符串哈希算法
template <>
struct HashFunc<string> //特化string类型的仿函数
{
static size_t BKDRHash(const char * str)
{
unsigned int seed = 131; // 31 131 1313 13131 131313
unsigned int hash = 0;
while (*str)
{
hash = hash * seed + (*str++);
}
return (hash & 0x7FFFFFFF);
}
size_t operator()(const string& key)
{
return BKDRHash(key.c_str());
}
};
二次探测实现
bool Insert(const K& key, const V& value)
{
_CheckCapacity();
size_t index = _HashFunc(key);
size_t first = index;
size_t i = 1;
if (index >= 0)
{
while (_table[index]._state == EXIT)
{
if (_table[index]._key == key)
return false;
index = (first + i*i) % _table.size();
++i;
if (index == _table.size())
index = 0;
}
_table[index]._key = key;
_table[index]._value = value;
_table[index]._state = EXIT;
++_size;
return true;
}
return false;
}
- 插入运行结果如下所示: