散列表

         或称Hash表或哈希表,通过科学的取地址方式,存放数据并便于查找。由此降低地址冲突的一个数据模式。

 

算法的实质:

完成   元素值x—>存储地址d的变换f,即   d=f(x)

 

(1)直接地址存储法

条件:元素取值范围与集合大小n差不多。

比如:定义数组arr[10],来存放10个自然数。

 

(2)散列存储法

条件:元素取值范围远远大于集合元素个数n。

比如:定义数组arr[10],来存放5个自然数。那么又怎么存放时最合理的,最利于查询的?这也是散列表的作用。

 

基本做法:

用数组a[m] 存储n个元素(m≥n)

存储元素x时,通过计算x的散列函数值:

     h=hash(x)   //0≤h≤m-1,将元素x存储在a[h]中

若任何xy,都有hash(x)hash(y),所设计的散列函数很均匀。查找x时,就能在a[hash(x)]中找到元素x。如果xy,而hash(x)=hash(y),则称x与y发生了碰撞 (Collision),或冲突。需要解决冲突。

 

下面介绍五种常用方法:

1. 取余法

2.提取数位法

3.平方取中法

4.折叠法

5.变换基数法

 

1.取余法

散列公式形如:hash(x)=x mod p

注意:

1. 模数p选取不大于m的常数 (m为数组的长度)。

2. 0≤hash(x)≤p-1≤m-1,保证地址不越界!

3. 通常情况下,模数p是一个接近m的最大素数时,散列结果比较均匀。

例如:元素个数n≤1000,散列表长度m=1024 

取模数p=1019 

hash(19450219)=566 

hash(19761203)=755 

 

2.提取数位法

一个很大数值按一定规律提取几个位数上的数字,组成一个新的数字,来代表这个数组。或者数值可能很小,也是利用二进制、八进制、十六进制等方式来提取。如图:

java散列其它的地址_散列函数

java散列其它的地址_解决冲突_02

 

3.平方取中法

该方法就是第一、二种方法相结合,让原来的数值平方后,在按第二种方式随机取几位数再组成一个新的地址。这方法的平法的可能会很大。不常用,在此就不再加以介绍。

 

4.折叠法

把元素x分成若干段,折叠后相加作为x的散列地址

折叠方向:顺向,反向,或者正反向交替

相加:算术相加(带进位加法)

      按位相加(无进位加法) 

例如,x=64912387(八位十进制),取三位地址 

几种不同的折叠方向和相加方法结果如下:

基本方法:分三段,每段三位,不足三位空缺

第一种移位叠加法----每部分最低位对齐

将x=64912387分成649、123和87,两种相加方法

java散列其它的地址_java散列其它的地址_03

 

 

第二种折界叠加法----沿分割界来回对折,对齐相加

将x=64912387分成649、321和87,两种相加方法 

 

java散列其它的地址_java散列其它的地址_04

注意:x=6491238分成649、321和87,此时将中间一段反向,即123变成321。

 

第三种折叠方法:

将x=64912387分成64、219和783,两种相加方法

java散列其它的地址_散列表_05

注意:将x=64912387分成64、219和783,此时将后面的两段反向,即912变成219,387变成783.

 

三种方法的比较:

这三种方法的本质是一样 ,把一个数拆分成几段,正向或反向部分,如:第一种:正向,不需要折叠,只把原数拆分;第二种:反向,将中间的部分反向;第三种:反向,将后面的两段反向。要注意:这方法的拆分是自拟的,第一、二种从左边开始,而第三种则从右边开始,根据个人习惯爱好。

 

5.变换基数法

将十进制数x看作其他进制(比如十三进制),再按十三进制数转换成十进制数,提取其中若干位作为x的散列值。

例如,

hash(80127429)10-----十进制

=( 80127429)13=8×137+0×136+1×135+2×134+7×133+4×132+2×13+9

=(502432641)10

再从(502432641)10这十进制数中任选几位作为散列函数。如:中间三位432.

 

6.混合法

将几种方法混合使用,比如,先变基,再折叠,再平方取中。。。

 

散列表的处理算法

1.构造和插入

构造:从空表开始,逐一插入

插入:通过计算元素x的散列函数值和解决冲突方法,为x找到一个空单元,存储x。

2.查找

查找:通过计算元素x的散列函数值和解决冲突方法,沿着x的插入路径就能找到x,插入路径就是查找路径,插入算法和查找算法步骤大体相同,

散列表的查找算法

步骤1)计算散列函数值h=hash(x);

步骤2)if(a[h]==x)return  h;         //查找成功

步骤3)if(a[h]==0)return  返回查找失败信息;

步骤4)按插入时采用的冲突处理方法进行查找

 

 

处理冲突

在上面提过,设计散列函数时不可能完全不冲突,因此就要处理这些冲突,有以下方法:

1. 链接法:将互相冲突的元素构成一个链表(处理方法同一般链表)

2. 开放地址法(二次散列法)

基本方法:

当发生冲突时,反复用探测的方法在散列表中寻找下一个结点

插入时,遇到空白结点即可进行插入。

查找时,直至找到要查找的元素(查找成功)。若遇到空白结点,表示查找失败。

 

 

第一种:线性探测具体步骤如图:

java散列其它的地址_散列表_06

java散列其它的地址_java散列其它的地址_07

java散列其它的地址_解决冲突_08

java散列其它的地址_java散列其它的地址_09

java散列其它的地址_散列表_10

java散列其它的地址_散列函数_11

线性探测的特点

优点:简单,可环视一周寻找空单元

缺点:易产生聚集现象,增加“冲突链”长度

            二次散列效果不佳

原因:增量的“步长”为1

改进措施:加大步长

 

 

 

第二种:平方探测的具体步骤:

 

java散列其它的地址_java散列其它的地址_12

java散列其它的地址_java散列其它的地址_13

java散列其它的地址_解决冲突_14

java散列其它的地址_散列函数_15

java散列其它的地址_散列表_16

 

 

 

第二种:法二:平方探测的改进步骤:

java散列其它的地址_java散列其它的地址_17

java散列其它的地址_解决冲突_18

 

 

第三种:随机探测的具体步骤:

java散列其它的地址_解决冲突_19

java散列其它的地址_解决冲突_20

java散列其它的地址_散列函数_21

java散列其它的地址_散列函数_22

java散列其它的地址_散列函数_23

 

 

散列表的删除方法(与解决冲突的方法有关)

1.使用链接法解决冲突时,与一般链表删除方法相似

2.用开放地址法解决冲突时,较难删除

(1)不能简单地置删除结点为空,这样会切断探测“链”

(2)可以对删除结点作“已删除标记”

  查找时跳过这些结点;插入时可以重新使用这些结点

(3)定期对整个表重新散列

 

 

 

java散列其它的地址_java散列其它的地址_24