哈希冲突-----闭散列与开散列
闭散列:
也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。
#include <iostream>
using namespace std;
// 哈希表每个空间给个标记
// EMPTY此位置空, EXIST此位置已经有元素, DELETE元素已经删除
enum State
{
EMPTY
, EXIST
, DELETE
};
// 哈希函数采用处理余数法,被模的key必须要为整形才可以处理,此处提供将key转化为整形的方法
// 整形数据不需要转化
/*template<class T>
class DefHashF
{
public:
size_t operator()(const T& val)
{
return val;
}
};
// key为字符串类型,需要将其转化为整形
class Str2Int
{
public:
size_t operator()(const string& str)
{
return BKDRHash(str.c_str());
}
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);
}
};*/
//线性探测实现
// 为了实现简单,此哈希表中我们将比较直接与元素绑定在一起
template<class K, class V>
class HashTable
{
struct Elem
{
pair<K, V> _val;
State _state;
};
public:
HashTable(size_t capacity = 3)
: _ht(capacity)
, _size(0)
{
for (size_t i = 0; i < capacity; ++i)
_ht[i]._state = EMPTY;
}
bool Insert(const pair<K, V>& val)
{
// 检测哈希表底层空间是否充足
// _CheckCapacity();
size_t hashAddr = HashFunc(key);
// size_t startAddr = hashAddr;
while (_ht[hashAddr]._state != EMPTY)
{
if (_ht[hashAddr]._state == EXIST && _ht[hashAddr]._val.first == key)
return false;
hashAddr++;
// hashAddr %= _ht.capacity();
if (hashAddr == _ht.capacity())
hashAddr = 0;
/*
// 转一圈也没有找到,注意:动态哈希表,该种情况可以不用考虑,哈希表中元素个数到达
一定的数量,哈希冲突概率会增大,需要扩容来降低哈希冲突,因此哈希表中元素是不会存满的
if(hashAddr == startAddr)
return false;
*/
}
// 插入元素
_ht[hashAddr]._state = EXIST;
_ht[hashAddr]._val = val;
_size++;
return true;
}
int Find(const K& key)
{
size_t hashAddr = HashFunc(key);
while (_ht[hashAddr]._state != EMPTY)
{
if (_ht[hashAddr]._state == EXIST && _ht[hashAddr]._val.first == key)
return hashAddr;
hashAddr++;
}
return hashAddr;
}
bool Erase(const K& key)
{
int index = Find(key);
if (-1 != index)
{
_ht[index]._state = DELETE;
_size++;
return true;
}
return false;
}
size_t Size()const
{
return _size;
}
bool Empty() const
{
return 0 == _size;
}
void Swap(HashTable<K, V, HF>& ht)
{
swap(_ht, ht._ht);
swap(_size, ht._size);
}
private:
size_t HashFunc(const K& key)
{
return key % _ht.capacity();
}
/*private:
size_t HashFunc(const K& key)
{
return HF()(key) % _ht.capacity();
}
*/
private:
vector<Elem> _ht;
size_t _size;
};
线性探测优点:实现非常简单,
线性探测缺点:一旦发生哈希冲突,所有的冲突连在一起,容易产生数据“堆积”,即:不同关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降低。
二次探测
线性探测的缺陷是产生冲突的数据堆积在一块,这与其找下一个空位置有关系,因为找空位置的方式就 是挨着往后逐个去找,因此二次探测为了避免该问题,找下一个空位置的方法为: = ( + )% m, 或者: = ( - )% m。其中:i = 1,2,3…, 是通过散列函数Hash(x)对元素的关键码 key 进行 计算得到的位置,m是表的大小。
当表的长度为质数且表装载因子a不超过0.5时,新的表项一定能够插入,而且任何一个位置都不会被探查两次。因此只要表中有一半的空位置,就不会存在表满的问题。在搜索时可以不考虑表装 满的情况,但在插入时必须确保表的装载因子a不超过0.5,如果超出必须考虑增容。
开散列
开散列概念 开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码 归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结 点存储在哈希表中
开散列实现
#include <iostream>
using namespace std;
// 哈希表每个空间给个标记
// EMPTY此位置空, EXIST此位置已经有元素, DELETE元素已经删除
enum State
{
EMPTY
, EXIST
, DELETE
};
// 哈希桶中元素是用链表串接起来的,因此先给出哈希桶的结构
template<class V>
struct HashBucketNode
{
HashBucketNode(const V& data)
: _pNext(nullptr)
, _data(data)
{}
HashBucketNode<V>* _pNext;
V _data;
};
template<class V, class HF = DefHashF<T> >
class HashBucket
{
typedef HashBucketNode<V> Node;
typedef Node* PNode;
public:
HashBucket(size_t capacity = 3)
: _size(0)
{
_ht.resize(GetNextPrime(capacity), nullptr);
}
// 哈希桶中的元素不能重复
PNode* Insert(const V& data)
{
// 确认是否需要扩容。。。
// _CheckCapacity();
// 1. 计算元素所在的桶号
size_t bucketNo = HashFunc(data);
// 2. 检测该元素是否在桶中
PNode pCur = _ht[bucketNo];
while (pCur)
{
if (pCur->_data == data)
return pCur;
pCur = pCur->_pNext;
}
// 3. 插入新元素
pCur = new Node(data);
// 采用头插法插入,效率高
pCur->_pNext = _ht[bucketNo];
_ht[bucketNo] = pCur;
_size++;
return pCur;
}
// 删除哈希桶中为data的元素(data不会重复),返回删除元素的下一个节点
PNode* Erase(const V& data)
{
size_t bucketNo = HashFunc(data);
PNode pCur = _ht[bucketNo];
PNode pPrev = nullptr;
pNode pRet = nullptr;
while (pCur)
{
if (pCur->_data == data)
{
// 头删
if (pCur == _ht[bucketNo])
{
_ht[bucketNo] = pCur->_pNext;
}
比特科技
else
{
pPrev->_pNext = pCur->_pNext;
}
pRet = pCur->_pNext;
delete pCur;
_size--;
return pRet;
}
}
return nullptr;
}
// 查找data是否在哈希桶中
PNode* Find(const V& data)
{
size_t bucketNo = HashFunc(data);
PNode pCur = _ht[bucketNo];
while (pCur)
{
if (pCur->_data == data)
return pCur;
pCur = pCur->_pNext;
}
return nullptr;
}
size_t Size()const
{
return _size;
}
bool Empty()const
{
return 0 == _size;
}
void Clear()
{
for (size_t bucketNo = 0; bucketNo < _ht.capacity(); ++bucketNo)
{
PNode pCur = _ht[bucketNo];
while (pCur)
{
_ht[bucketNo] = pCur->_pNext;
delete pCur;
pCur = _ht[bucketNo];
}
}
_size = 0;
}
bool BucketCount()const
{
return _ht.capacity();
}
void Swap(HashBucket<V, HF>& ht)
{
swap(_ht, ht._ht);
swap(_size, ht._size);
}
~HashBucket()
{
Clear();
}
private:
size_t HashFunc(const V& data)
{
return HF()(data) % _ht.BucketCount();
}
private:
vector<PNode*> _ht;
size_t _size; // 哈希表中有效元素的个数
};
开散列增容
桶的个数是一定的,随着元素的不断插入,每个桶中元素的个数不断增多,极端情况下,可能会导致一 个桶中链表节点非常多,会影响的哈希表的性能,因此在一定条件下需要对哈希表进行增容
开散列最好的情况是:每个哈希桶中刚好挂一个节点,再继续插入元素时,每一次都会发生哈希冲突,因此,在元素个数刚好等于桶的个数时,可以给哈希表增容。
/*void _CheckCapacity()
{
size_t bucketCount = BucketCount();
if (_size == bucketCount)
{
HashBucket<V, HF> newHt(bucketCount);
for (size_t bucketIdx = 0; bucketIdx < bucketCount; ++bucketIdx)
{
PNode pCur = _ht[bucketIdx];
while (pCur)
{
// 将该节点从原哈希表中拆出来
_ht[bucketIdx] = pCur->_pNext;
// 将该节点插入到新哈希表中
// 计算该节点在新哈希表中的桶号
size_t bucketNo = newHt.HashFunc(pCur->_data);
// 找节点在新桶中的位置
PNode pPos = newHt._ht[bucketNo];
while (pPos)
{
if (pPos->_data == pCur->_data)
break;
pPos = pPos->_pNext;
}
// 将节点插入到新哈希表中
if (nullptr == pPos)
{
pCur->_pNext = newHt._ht[bucketNo];
newHt._ht[bucketNo] = pCur;
}
else
{
pCur->_pNext = pPos->next;
pPos->_pNext = pCur;
}
// 获取原哈希表bucketIdx桶中下一个节点
pCur = _ht[bucketIdx];
}
}
newHt._size = _size;
this->Swap(newHt);
}
}
*/
上一篇: Java集合面试题