数据结构之哈希表

来源:互联网 发布:万网域名主机记录 编辑:程序博客网 时间:2024/06/08 14:56

       哈希表是一种数据结构,可以提供快速的插入和查找操作。哈希表的速度明显比树快,树的操作通常需要O(N)的时间级,而哈希表只需要接近常量的时间:即O(1).哈希表不仅速度快,编程实现也相对容易。哈希表也有缺点:它是基于数组的,数组创建之后,难于扩展。某些哈希表被基本填满时,性能下降的非常严重,所以必须要清楚表中将要存储多少数据(或者准备好定期地把数据转移到更大的哈希表中,这是个费时的过程)。而且,也没有一种简便的方法可以以任何一种顺序(例如从小到达)遍历表中数据项。如果需要这种能力,就只能选择其他数据结构。然而,如果不需要有序遍历数据,并且可以提前预测数据量的大小,那么哈希表在速度和易用性方便是无与伦比的。

       哈希表和哈希化有一个重要的概念是如何把关键字转换成数组下标,在哈希表中这个转换是通过哈希函数来完成的。比如规定一个单词含有4个字符,对于单词cats,我们采用“幂的连乘”的方式将一个单词映射成数字。因为有27个可能的字符,包括空格,所以幂采用27,则cats的数字下标是3*27^3+1*27^2+20*27^1+19*27^0 = 60337.如果规定一个单词含有10个字符,则zzzzzzzzzz的数组下标是一个非常大的数字,在内存中的数组根本不可能有这么多的内存单元。解决的方式是哈希(转换)化,将一个非常大的范围的数字压缩到可接受的数组范围中。比如arrayIndex = hugeNumber % arraySize;这就是一个哈希函数,使用哈希函数向数组插入数据之后,这个数组就被称为哈希表。

       通过哈希化方案得到的数组下标存在冲突问题,即不同的元素对应的数组下标是同一个。解决方式有两种:开放地址法和链地址法。

       1、开放地址法:若数据不能直接放在由哈希函数计算出来的数组下标所指的单元时,就要寻找数组的其他位置。目前,开放地址法的三种方法分别是线性探测、二次探测和再哈希法。

       a)线性探测:在线性探测中,线性地查找空白单元。如果5421是要插入数据的位置,它已经被占用了,那么就使用5422,然后5423,依此类推,数组的下标一直递增,直到找到空位。这就要线性探测法。

       b)二次探测:在开放地址法的线性探测中会发生聚集(数据集中分布),一旦聚集形成,会变得越来越大,越大则探测的长度会越长。聚集降低了哈希表的性能。二次探测是防止聚集产生的一种尝试。思想是探测相隔较远的单元,而不是和原始位置相邻的单元。在线性探测中,如果哈希函数计算的原始下标是x,线性探测就是x+1,x+2,x+3,依此类推。而在二次探测中,探测的过程是x+1,x+4,x+9,x+16,x+25,依次类推。虽然二次探测法消除了在线性探测中产生的聚集问题,但是仍然会产生更细的聚集问题。

       c)再哈希法:为了消除原始聚集和二次聚集,可以使用另外的一个方法:再哈希法。原始聚集和二次聚集产生的原因是探测序列的步长总是固定的。因此现在需要的一种方法是产生一种依赖关键字的探测(步长)序列,而不是每个关键字都是一样的固定步长。具体做法是把关键字用不同的哈希函数再做一遍哈希化,用这个结果作为步长。对于指定的关键字步长在整个探测过程中是不变的,不过不同的关键字使用不同的步长。因此,再哈希法中使用了两个哈希函数:一个用于找到原始位置,另一个生成步长。例如:


       2、链地址法

       开放地址法中,通过在哈希表中再寻找一个空位解决冲突问题。另一个方法是在哈希表每个单元中设置链表。某个数据项的关键字值还是像通常一样映射到哈希表的单元,而数据项本身插入到这个单元的链表中,而不需要在原始的数组中寻找空位。


       另外一种方法类似于链地址法,它在哈希表的每个单元中使用数组,而不是链表。这样的数组有时成为桶。然而,这个方法不如链表有效,因为桶的容量不好选择。如果容量太小,可能会溢出。如果太大,又浪费空间。链表是动态分配的,所以没有这个问题。

       哈希函数的目的是得到关键字的范围,把它用一种方式转换成数组的下标值。

原创粉丝点击