1.常见的哈希函数
1.1 直接定址法
取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B
1.2 除留余数法
设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函 数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址
2. 闭散列
闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那 么可以把key存放到冲突位置中的“下一个” 空位置中去。那如何寻找下一个空位置呢?
- 线性探测
从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。
来看下面的例子:
先插入1,2,3,4,5,6,8;现在要插入44.44 % 10 = 4,显然4已经被占用,此时就一直探测到空位置7,进行插入。
bool Insert(const pair<K, V>& data)
{
CheckCapcity();
int index = data.first % _ht.size();
while (_ht[index]._state == Exist)
{
if (_ht[index]._data.first == data.first)
return false;
++index;
if (index == _ht.size())
index = 0;
}
_ht[index]._data = data;
_ht[index]._state = Exist;
++_size;
return true;
}
- 二次探测
线性探测的缺陷是产生冲突的数据堆积在一块,这与其找下一个空位置有关系,因为找空位置的方式就 是挨着往后逐个去找,因此二次探测为了避免该问题,找下一个空位置的方法为: = ( + )% m, 或者: = ( - )% m。其中:i = 1,2,3…, 是通过散列函数Hash(x)对元素的关键码 key 进行 计算得到的位置,m是表的大小。
2. 开散列
开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码 归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结 点存储在哈希表中。
那么上述的例子插入,将会变成这样:
即出现哈希冲突时,它会找到这个链表进行头插。
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;
}