哈希表(Hash table)

来源:互联网 发布:java观察者设计模式 编辑:程序博客网 时间:2024/05/20 11:53

//...................哈希表........................

1.什么是哈希表?

哈希表(Hash table,又叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。即,它通过吧关键码值映射到表中的某一个位置来存储元素,然后根据关键码用同样的方式直接访问,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

2.哈希表做法:把Key通过一个固定的算法函数即哈希函数转换成一个整型数字,然后就将该数字对数组长度进行取余,取余结果就当做数组的下标,将value存储在以该数字为下标的数组空间中。即在关键码和存储位置之间直接建立了映像。

当时用哈希表进行查询的时候,就是再次使用哈希函数将key转换为对应的数组下标,并定位到该空间获取value.

3.哈希表优点:

*****数组的特点是:寻址容易,插入和删除困难。

       链表的特点是:寻址困难,插入和删除容易。

哈希表结合了二者的优点,做到了寻址容易,插入删除也容易。

4.哈希表的实现方式----拉链法

如图:

左边是个数组,数组的每个成员包含一个指针,指向一个链表的头,这个链表可能为空,也可能有很多元素,

我们根据元素的一些特征将元素分配到不同的链表中,也是根据这些特征,找到正确的链表,从链表中找出这个元素。

散列法一:除留余数法

index = value%p;  //对元素取模运算。

其中p不是接近2的幂

上图 index = value%16.

散列法二:平方散列法:

index = (value*value)>>28

注:>>(右移)相当于除以2^28,

       <<(左移)相当于乘以2^28

例如 4>>1  0100(4) -- 0010(2)  相当于4除以2^1,结果为2

        2<<2  0010(2)  ---1000(8) 相当于2乘以2^2,结果为8

综上:右移n位相当于除以2^n,左移n位相当于乘以2^n.

平方散列法适用于数值分配比较均匀的数据。

散列法三--斐波那契散列法

斐波那契数列:0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,……

平方散列法是拿value本身作为乘数,这里我们找一个理想的乘数

1.对于16位整数而言,这个乘数是40503,

2.对于32位整数而言,这个乘数是2654435769

3.对于64位整数而言,这个乘数是11400714819323198485

这几个数字是由黄金分割法则所得来的,描述黄金分割法则的最经典表达式就是斐波那契数列。


例如对于我们常见的32位整数:

index = (value*2654435769)>>28

如图:



 5.哈希表的冲突:当关键字值域远大于哈希表的长度,而且事先并不知道关键字的具体取值时,就会发生冲突

                              另外,当关键字的实际取值远大于哈希表的长度,而且表中已装满了记录,此时插入一个新纪录,                               不仅发生冲突,还会发生溢出。

6.处理哈希冲突的方法(溢出处理技术):避免和解决,即冲突避免机制和冲突解决机制

 ---避免哈希冲突的方法是选择合适的哈希函数。

 ---解决哈希冲突的方法:

1.开放寻址法(Open Addressing):

将所有的元素都存放在哈希表数组中,不使用额外的数据结构。

开放寻址法最简单的一种实现就是线性探查法,步骤如下:

  ~当插入新的元素时,使用哈希函数在哈希表中定位元素位置。

  ~检查哈希表中该位置是否已经存在元素,如果该位置内容为空,则插入并返回,否则,转向步骤3.

  ~如果该位置为i,则检查i+1是否为空,如果已被占用,则检查i+2,依次类推,直到找到一个内容为空的位置并插入。


例子如下:

给出一组数据,它们的关键码是37,25,14,36,49,68,57,11,散列表为hash[12];

表的大小为m = 12,采用的散列函数 Hash(x) = x%11;   其中11是小于等于m最接近m的质数。

采用线性探查法得到关键码在散列表中散列位置如下:

线性探查法处理冲突0123456789101111 68253714364957   线性探查的方式虽然简单,但并不是解决冲突最好的策略,因为会导致同类哈希的聚集。即不同探查序列的关键码占据了可利用的空桶,使得为寻找某一个关键码需要经历不同的探查序列的元素。导致搜索时间增加。

为此,可采用二次探查法,改善上述的“聚集”问题,减少为完成搜索所需的平均探查次数。

2.二次探查法:即每次检查位置空间的步长为平方倍数。

步骤为:

  ~当插入新的元素时,使用哈希函数在哈希表中定位元素位置。

  ~检查哈希表中该位置是否已经存在元素,如果该位置内容为空,则插入并返回,否则,转向步骤3.

  ~如果该位置为i,则检查i+1^2是否为空,如果已被占用,则检查i-1^2,,i+2^2,i-2^2,依次类推,直到找到一个内容为空的位置并插入。

注:表的大小应该是一个值为4k+3的质数,其中k是一个整数。


例子:给出一组元素,它们的关键码为37,25,14,36,49,68,57,11.因为散列表的大小必须是满足4k+3的质数,可取m = 19,这样散列表可设定为HT[19],采用的散列函数:

Hash(x) = x%19;

采用二次探查法处理冲突得到关键码在散列表中散列位置如下:

012345678910111213141516171857     25   114968 14  3637

3.双散列法:使用双散列法时,需要两个散列函数。第一个散列函数Hash()按元素的关键码key计算元素所在的位置,一旦发生冲突,就利用第二个散列函数ReHash()计算该元素到达下一个未知的移位量。它的取值与key的取值有关,要求它的取值应该小于地址空间大小(m)且与m互质的正整数。

即:第一个散列函数为Hash(x) = x%m; 

  第二个散列函数为Hash(j) = (j+p)%m; 其中p是小于m且与m互质的整数。

利用双散列法,按一定的距离,跳跃式的寻找下一个位置,减少了“堆积”的机会。


解决哈希冲突的方法:

1.链地址法:首先对关键码集合用某散列函数计算它们的存储位置。若设散列表地址空间的所有位置0~m-1,则关键码集合中的所有关键码被划分为m个子集合,通过散列函数计算出来的具有相同地址的关键码归于同一子集合。

将散列到同一个地址的所有关键码都放到一个链表中。

通俗一点讲就是将哈希表中每个位置都映射到了一个链表。当冲突发生时,冲突的元素将被添加到同列表中,而每个同都包含了一个链表以存储相同哈希的元素。

例如:

现在如果我们要将五个员工的信息插入到哈希表中:

  • Alice (333-33-1234)
  • Bob (444-44-1234)
  • Cal (555-55-1237)
  • Danny (000-00-1235)
  • Edward (111-00-1235)

上图中的哈希表包含了8个桶,也就是黄色背景的位置,如果一个新的元素要添加至哈希表中,将被添加至key的哈希所对应的桶中,如果在相同位置已经有元素存在,则将新元素添加到列表的前面。

使用链接技术添加元素的操作涉及到哈希计算和链表操作,但其仍为常量,渐进时间为 O(1)。而进行查询和删除操作时,其平均时间取决于元素的数量和桶(bucket)的数量。具体的说就是运行时间为 O(n/m),这里 n 为元素的总数量,m 是桶的数量。但通常对哈希表的实现几乎总是使 n = O(m),也就是说,元素的总数绝不会超过桶的总数,所以 O(n/m) 也变成了常量 O(1)。


//..........................................全域哈希

全域哈希法是指:随机的选择哈希函数,使之独立于要存储的元素,这种方法称作全域哈希。

全域哈希目的是为了改进在向哈希表中插入元素时,如果所有的元素全部被哈希到一个桶中,此时数据的存储实际上就是一个链表,那么平均的查找事件为O(n),为了改进,我们采取全域哈希法。

基本思想:在执行开始时,从一组哈希函数中,随机的抽取一个要使用的哈希函数,就像在快速排序一样,随机化保证了没有哪一种输入会始终导致最坏情况的发生。同时,随机化也使得即使是对同一个输入,算法在每一次的执行时的情况也都不一样。这样就确保了对于任何输入,算法都具有较好的平均运行情况。










原创粉丝点击