概念:
Hash 是“杂乱信息”的意思。
Hash表是将一堆杂乱得信息,根据它们的关键词特点,将其映射到一个连续的空间上。这种映射关系称作索引方法 ,对应的实现函数叫哈希函数,同时将关键词映射后的值叫做关键词的索引。
索引实际上就是数组的下标,数组的每一个位置习惯上也被称作桶(bucket),在哈希表中桶和索引的叫法是等价的。
哈希表的索引方法:
- 整除取余法
/** 哈希表的计算整数的索引函数
@param UINT uKey——关键词
@param UINT uBucketCount——哈希表的大小,用来做整除的模
@return UINT——索引值
*/UINT HashIndexClac_Int(UINT KeyValue, UINT ModelValue)
{
UINT Index = 0;
Index = KeyValue % ModelValue;
return Index;
}优点 :一个连续的序列或者接近一个连续系列,效果最好。
缺点 :1)需要求出一个使关键词索引尽量不相同的最小 nMod 来。当然 nMod 不能太大,因为最后得出的余数可能为 nMod-1,所以表的大小必须大于 nMod-1,当 nMod 太大时意味着需要表的辅助空间太大。
2)只能对计算机硬件能够处理的整数进行计算,如32 位 CPU 硬件最大能处理的整数是 2^32 =4294964296
- 折叠法
当关键词位数很多时,可以将关键词分割成位数相同的几部分,每部分可以转换成一个计算机可以处理的整数,然后将各部分转换后的整数相加,得到一个新的整数,这就是折叠法。折叠法最后得到的那个新的整数不能直接作为关键词的索引,还需要使用整除取余法来得到关键词的索引。
/** 字符串类型数据的关键词索引计算函数,将字符串折叠成每 5 个字符一段,每段看成
八进制整数,将各段相加后再取余数
@param void *pStr——字符串指针
@param UINT uBucketCount——最大 bucket 的个数,被用来做为整除的模
@return UINT——字符串关键词的索引
*/
UINT HashIndexClac_String(VOID * StringValue, UINT ModelValue)
{
unsigned char *psz;
UINT HashValue = 0;
UINT Ret = 0;
UINT i = 0;
UINT t = 0;
psz = (unsigned char *)StringValue;
while (*psz != '\0')
{
if (i == 5)
{
i = 0;
Ret += HashValue;
HashValue = 0;
}
t = HashValue << 3;
HashValue = HashValue + t;
HashValue = HashValue + (UINT)(*psz);
psz++;
i++;
}
Ret += HashValue;
return HashIndexClac_Int(Ret, ModelValue) ;
}
采用折叠法的计算开销比整除取余法大很多,因为它要将关键词中的每个字节都计算一遍,但它的好处是计算出的索引很少会重复。在数据较多的情况下,使用这种方法效果是很好的。
- 平方取中法
顾名思义,平方取中法是将关键词进行平方运算后,再取运算结果的中间几位作为索引。通常一个数经平方运算后,其结果的中间几位和数的每一位都相关,具体取多少位则需要由表的长度来决定。
- 随机函数法
随机函数法是设计一个产生随机数的函数,以关键词作为随机函数的输入,函数的计算结果作为索引。采用随机函数法,在产生随机数时一般也要用到整除取余,实际上是整除取余法的一个扩展。
不管采用何种方法计算索引,都会对以下几个方面产生影响:
① 计算索引所需要的时间(建议最大不要超过 12 次比较的时间);
② 每次查找关键词的平均比较次数(建议最大不要超过 5 次);
③ 最坏情况下的比较次数(建议最大不要超过 50 次);
④ 哈希表所需要的辅助空间 (建议不要超过实际关键词个数的两倍大小)。
哈希冲突
原因:由于哈希算法被计算的数据是无限的,而计算后的结果范围有限,因此总会存在不同的数据经过计算后得到的值相同,这就是哈希冲突。
解决方法:
- 开放定址法
将所有的输入都元素全部放在哈希表中,不需要任何链表来完成,当发生哈希冲突饿时,根据再寻址方法,去寻找下一个地址,直到找到一个空位置。
再寻址方法:
- 线性探测
按顺序决定哈希值时,如果某数据的哈希值已经存在,则在原来哈希值的基础上往后加一个单位,直至不发生哈希冲突。
- 二次探测
按顺序决定哈希值时,如果某数据的哈希值已经存在,则在原来哈希值的基础上先加1的平方个单位,若仍然存在则减1的平方个单位。随之是2的平方,3的平方等等。直至不发生哈希冲突。
- 伪随机探测
按顺序决定哈希值时,如果某数据已经存在,通过随机函数随机生成一个数,在原来哈希值的基础上加上随机数,直至不发生哈希冲突。
- 链地址法
对于相同的哈希值,使用链表进行连接。
优点:
(1)拉链法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短;
(2)由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;
(3)开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间。而拉链法中可取α≥1,且结点较大时,拉链法中增加的指针域可忽略不计,因此节省空间;
(4)在用拉链法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。
缺点:指针占用较大空间时,会造成空间浪费,若空间用于增大散列表规模进而提高开放地址法的效率。
- 公共溢出区
将所有产生哈希冲突的数据储存再公共溢出区。
- 再HASH法
对产生哈希冲突的数据,重新哈希计算,直到没有哈希冲突。时间、性能会有影响。