有两种策略来解决hash table的碰撞问题。第一种策略是open addressing,若是数组中当前位置已经被占用,他会为当前数据从新选择一个位置;第二种策略是separate chaining,在数组每个位置安放一个链表。 html
1.Open Addressing 算法
若是当前数据所修位置被占用,该策略会为数据从新选择一个位置。这种测略有三种方法可使用: 数组
a.Liner probing 函数
这种方法简单的以当前位置为起点,线性搜索空闲位置。若是hash函数选择了第n个位置给当前数据,可是n位置已经被占用,该方法将会尝试n+1,n+2.........,知道找到一个空闲位置,若是到达数组末尾,则从数组头继续搜索。这个尝试不一样位置的过程叫作probing。 spa
之因此叫作Liner probing,是由于这个过程看起来像线性搜索。 指针
当须要在hash table中搜索数据时候,一样要是用probing,当经过key来搜索数据,且hash函数获得的位置是x,若是x位置包含其余的数据,那么继续搜索x+1,x+2,直到搜索到目标元素,或者x+k位置为空,或者又从新便利到x位置。 htm
Liner probing的问题是clustering(聚合),数据元素成块状分布。若是插入一个映射到x位置的值,可是x被占用了,插入方法将尝试x+1。若是下一个元素被映射到x+1,而这个位置被占用了,那他就得继续日后尝试。数据将会汇集成一块一块的。在实际应用中,若是插入数据类似,那么会产生数据聚合,致使搜索和插入数据效率降低。 blog
解决这个问题的方法是move ahead,不只仅是前移一个元素位置。在quadratic probing中,插入方法将尝试x+1^2,x+2^2,x+3^2.........直到找到空闲位置,在查找时候也要这么作。 get
b.Quadratic probing hash
Quadratic probing的好处是能够下降clustering,由于probing的偏移是n^2,而不是1,不会使得数据很接近。可是若是不少数据同时被映射到同一个位置上,那么这种方法也是于事无补的。当有不少数据被映射到同一个位置时候,他将会尝试x+1,x+4,x+9...............这将使数据查找变得困难。
c.double hashing
double hashing中,probing的偏移取决于key value本身。当key映射到位置x,而x已经被占用的时候,则用第二个hash函数对key进行处理获得y,尝试x+y,x+2y,x
+3y..........直到找到一个能够插入的位置。选择第二个hash函数的目标是:hash函数值永远大于等于1,对于key的获得的hash结果和第一个hash函数不一样。一般,第二个hash函数这么写:
probe offset = c - key%c, c是一个小于数组大小的常数
2.Separate chaining
这种方法在数组的每个位置上用一个链表
/////////////////////////////////////////////////////////////////////////
“处理冲突”的含义是:为产生冲突的关键字寻找下一个哈希地址。一般有两类方法处理冲突:开放定址(Open Addressing)法和拉链(Chaining)法。前者是将全部结点均存放在散列表T[0..m-1]中;后者一般是将互为同义词的结点链成一个单链表,而将此链表的头指针放在散列表T[0..m-1]中。
1 开放地址法
这个方法的基本思想是:当发生地址冲突时,按照某种方法继续探测哈希表中的其余存储单元,直到找到空位置为止。这个过程可用下式描述:
H i ( key ) = ( H ( key )+ d i ) mod m ( i = 1,2,…… , k ( k ≤ m – 1))
其中: H ( key ) 为关键字 key 的直接哈希地址, m 为哈希表的长度, di 为每次再探测时的地址增量。
采用这种方法时,首先计算出元素的直接哈希地址 H ( key ) ,若是该存储单元已被其余元素占用,则继续查看地址为 H ( key ) + d 2 的存储单元,如此重复直至找到某个存储单元为空时,将关键字为 key 的数据元素存放到该单元。
增量 d 能够有不一样的取法,并根据其取法有不一样的称呼:
( 1 ) d i = 1 , 2 , 3 , …… 线性探测再散列;
( 2 ) d i = 1^2 ,- 1^2 , 2^2 ,- 2^2 , k^2, -k^2…… 二次探测再散列;
( 3 ) d i = 伪随机序列 伪随机再散列;
例1设有哈希函数 H ( key ) = key mod 7 ,哈希表的地址空间为 0 ~ 6 ,对关键字序列( 32 , 13 , 49 , 55 , 22 , 38 , 21 )按线性探测再散列和二次探测再散列的方法分别构造哈希表。
①线性探查法(Linear Probing)
该方法的基本思想是:
将散列表T[0..m-1]当作是一个循环向量,若初始探查的地址为d(即h(key)=d),则最长的探查序列为:
d,d+l,d+2,…,m-1,0,1,…,d-1
即:探查时从地址d开始,首先探查T[d],而后依次探查T[d+1],…,直到T[m-1],此后又循环到T[0],T[1],…,直到探查到T[d-1]为止。
探查过程终止于三种状况:
(1)若当前探查的单元为空,则表示查找失败(如果插入则将key写入其中);
(2)若当前探查的单元中含有key,则查找成功,但对于插入意味着失败;
(3)若探查到T[d-1]时仍未发现空单元也未找到key,则不管是查找仍是插入均意味着失败(此时表满)。
汇集或堆积现象
用线性探查法解决冲突时,当表中i,i+1,…,i+k的位置上已有结点时,一个散列地址为i,i+1,…,i+k+1的结点都将插入在位置i+k+1上。把这种散列地址不一样的结点争夺同一个后继散列地址的现象称为汇集或堆积(Clustering)。这将形成不是同义词的结点也处在同一个探查序列之中,从而增长了探查序列的长度,即增长了查找时间。若散列函数很差或装填因子过大,都会使堆积现象加重。
②二次探查法(Quadratic Probing)
二次探查法的探查序列是:
hi=(h(key)+i*i)%m 0≤i≤m-1 //即di=i2
即探查序列为d=h(key),d+12,d+22,…,等。
该方法的缺陷是不易探查到整个散列空间。
③双重散列法(Double Hashing)
该方法是开放定址法中最好的方法之一,它的探查序列是:
hi=(h(key)+i*h1(key))%m 0≤i≤m-1 //即di=i*h1(key)
即探查序列为:
d=h(key),(d+h1(key))%m,(d+2h1(key))%m,…,等。
该方法使用了两个散列函数h(key)和h1(key),故也称为双散列函数探查法。
注意:
定义h1(key)的方法较多,但不管采用什么方法定义,都必须使h1(key)的值和m互素,才能使发生冲突的同义词地址均匀地分布在整个表中,不然可能形成同义词地址的循环计算。
【例】 若m为素数,则h1(key)取1到m-1之间的任何数均与m互素,所以,咱们能够简单地将它定义为:
h1(key)=key%(m-2)+1
【例】对例9.1,咱们可取h(key)=key%13,而h1(key)=key%11+1。
【例】若m是2的方幂,则h1(key)可取1到m-1之间的任何奇数。
二、拉链法
(1)拉链法解决冲突的方法
拉链法解决冲突的作法是:将全部关键字为同义词的结点连接在同一个单链表中。若选定的散列表长度为m,则可将散列表定义为一个由m个头指针组成的指针数组T[0..m-1]。凡是散列地址为i的结点,均插入到以T[i]为头指针的单链表中。T中各份量的初值均应为空指针。在拉链法中,装填因子α能够大于1,但通常均取α≤1。
(2)拉链法的优势
与开放定址法相比,拉链法有以下几个优势:
(1)拉链法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,所以平均查找长度较短;
(2)因为拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前没法肯定表长的状况;
(3)开放定址法为减小冲突,要求装填因子α较小,故当结点规模较大时会浪费不少空间。而拉链法中可取α≥1,且结点较大时,拉链法中增长的指针域可忽略不计,所以节省空间;
(4)在用拉链法构造的散列表中,删除结点的操做易于实现。只要简单地删去链表上相应的结点便可。而对开放地址法构造的散列表,删除结点不能简单地将被删结点的空间置为空,不然将截断在它以后填人散列表的同义词结点的查找路径。这是由于各类开放地址法中,空地址单元(即开放地址)都是查找失败的条件。所以在用开放地址法处理冲突的散列表上执行删除操做,只能在被删结点上作删除标记,而不能真正删除结点。
(3)拉链法的缺点
拉链法的缺点是:指针须要额外的空间,故当结点规模较小时,开放定址法较为节省空间,而若将节省的指针空间用来扩大散列表的规模,可以使装填因子变小,这又减小了开放定址法中的冲突,从而提升平均查找速度。
三、创建一个公共溢出区
假设哈希函数的值域为[0,m-1],则设向量HashTable[0..m-1]为基本表,另外设立存储空间向量OverTable[0..v]用以存储发生冲突的记录。通过以上方法,基本能够解决掉hash算法冲突的问题。