//...................哈希表........................
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的质数。
采用线性探查法得到关键码在散列表中散列位置如下:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
11 | 68 | 25 | 37 | 14 | 36 | 49 | 57 |
为此,可采用二次探查法,改善上述的“聚集”问题,减少为完成搜索所需的平均探查次数。
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;
采用二次探查法处理冲突得到关键码在散列表中散列位置如下:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
57 | 25 | 11 | 49 | 68 | 14 | 36 | 37 |
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),为了改进,我们采取全域哈希法。
基本思想:在执行开始时,从一组哈希函数中,随机的抽取一个要使用的哈希函数,就像在快速排序一样,随机化保证了没有哪一种输入会始终导致最坏情况的发生。同时,随机化也使得即使是对同一个输入,算法在每一次的执行时的情况也都不一样。这样就确保了对于任何输入,算法都具有较好的平均运行情况。