简介
为了解决线性探测实现哈希表时出现哈希冲突后导致数据堆聚的现象,我们采取了另一种结构来处理哈希冲突,哈希桶(拉链法),拉链法解决冲突的做法是将所有通过哈希函数计算出来的哈希地址相同的结点存放在一个单链表之中。
实现原理就是将哈希表定义为一个由N个头指针组成的指针数组,经过哈希函数计算得到的哈希地址相同的数据全部连在对于的头指针下面。它继承了数组的易于查找和链表便于删除插入的特点。
时间复杂度
拉链法因为加入了链表,所以在查找删除的时候时间复杂度也退化为了O(N),但是如果在哈希函数给的合理,并且哈希表的容量足够的时候,拉链法的时间复杂度肯定是远远小于O(N)。
拉链法的优缺点
优点:
解决了哈希表堆聚现象,减小了平均查找长度。删除结点相对于线性探测更加易于实现,只需要找到相应结点再删除就可以了,而开放地址法中不能这样做,因为在哈希表中置空数组中的结点会导致后面的数据无法访问。
缺陷:
当数据量比较小的时候,开放地址法不用开辟空间,如果相对于拉链法节省的结点空间用来扩大散列表则可以使装填因子(结点数与表长的比值),这样也就减少了开放地址法的冲突,提高平均查找速度。
代码实现
代码在整体框架上的设计和线性探测基本一致,只是在插入删除的操作方式上有了区别,不是使用更改状态来表示删除,而是采用了链表的删除和插入操作。因为采取了链表和vector结合的做法,所以在结点设计上删去了_status(状态),增加了一个指向下一个结点的指针_next。
template<class K,class V>
struct HashTableBucketNode
{
K _key;
V _value;
HashTableBucketNode<K, V>* _next;
HashTableBucketNode(const K& key, const V& value)
:_key(key)
, _value(value)
, _next(NULL)
{}
};
template<class K>
struct _HashFunc
{//用来计算key的仿函数
size_t operator()(const K& key)
{
return key;
}
};
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());
}
};
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
};
template<class K,class V,class HashFunC = _HashFunc<K>>
class HashTableBucket
{
typedef HashTableBucketNode<K,V> Node;
public:
HashTableBucket(size_t size)
:_size(0)
{
size_t newsize = GetPrime(size);
_v.resize(newsize);
}
size_t HashFunc(const K& key)
{//哈希函数
HashFunC hs;
size_t hash = hs(key);
return hash % _v.size();
}
pair<Node*, bool> Insert(const K& key, const V& value)
{//插入
CheckCapacity();
size_t index = HashFunc(key);
if (!_v[index])
{//头指针为空
Node* newNode = new Node(key, value);
_v[index] = newNode;
++_size;
return make_pair(newNode, true);
}
//在链表中查找插入位置
Node* prev = NULL;
Node* cur = _v[index];
while (cur)
{
if (cur->_key == key)
return make_pair(cur, false);
prev = cur;
cur = cur->_next;
}
Node* newNode =new Node(key, value);
prev->_next = newNode;
++_size;
return make_pair(newNode, true);
}
//Node* Find(const K& key)
//{//查找的第一种实现,直接按位置查找
// size_t index = HashFunc(key);
// Node* cur = _v[index];
// while (cur)
// {
// if (cur->_key == key)
// return cur;
// cur = cur->_next;
// }
// return NULL;
//}
Node* Find(const K& key)
{//遍历查找
for (size_t index = 0; index < _size; ++index)
{
Node* cur = _v[index];
while (cur)
{
if (cur->_key = key)
return cur;
cur = cur->_next;
}
}
return NULL;
}
bool Remove(const K& key)
{//删除
size_t index = HashFunc(key);
Node* prev = NULL;
Node* cur = _v[index];
while (cur)
{
if (cur->_key == key)
{
if (prev == NULL)
_v[index] = NULL;
else
prev->_next = cur->_next;
delete cur;
return true;
}
}
return false;
}
protected:
size_t GetPrime(size_t size)
{//获得素数表素数
for (size_t i = 0; i < _PrimeSize; ++i)
{
if (_PrimeList[i] > size)
{
return _PrimeList[i];
}
}
return _PrimeList[_PrimeSize - 1];
}
void Swap(HashTableBucket<K, V> tmp)
{//交换
std::swap(_v, tmp._v);
std::swap(_size, tmp._size);
}
void CheckCapacity()
{//容量检测
if (_size == _v.size())
{
HashTableBucket<K, V> tmp(_v.size());
for (size_t index = 0; index < _size; ++_size)
{
Node* cur = _v[index];
while (cur)
{
Node* cur = _v[index];
while (cur)
{
tmp.Insert(cur->_key, cur->_value);
cur = cur->_next;
}
}
}
Swap(tmp);
}
}
private:
vector<Node*> _v;
size_t _size;
};