开放寻址法

开放寻址法的装载因子

  给定一个能存放n个元素的、具有m个槽位的哈希表T,采用开放寻址法时T的装载因子为: α = n / m , n ≤ m \alpha=n/m,n \leq m α=n/mnm

开放寻址法

  解决哈希表(在一些文献中又称作散列表)冲突的方法有:链接法(chaining)开放寻址法(open addressing)。本文讲解开放寻址法。
  在开放寻址法中,所有元素都存在哈希表里。也就是说,每个表项或包含动态集合的一个元素,或包含 N I L NIL NIL(空)。当查找某个元素时,要系统地检查所有的表项,直到找到所需的元素或者最终查明该元素不在表中。在开放寻址法中,哈希表可能被填满,以至于不能插入任何新的元素,该方法导致的一个结果便是装载因子 α \alpha α绝对不会超过1。
  开放寻址法的好处是其不使用指针,直接使用
哈希函数
来算出存取的位置,这样也可以加快存取速度。

插入关键字

  为了使用开放寻址法插入一个元素,需要连续地检查哈希表,或称为探查(probe),直到找到一个空槽来放置待插入的关键字为止。检查的顺序依赖待插入的关键字。对于每一个关键字 k k k,使用开放寻址法探查序列为 < h ( k , 0 ) , h ( k , 1 ) , ⋯   , h ( k , m − 1 ) > <h(k,0), h(k,1),\cdots, h(k,m-1)> <h(k,0),h(k,1),,h(k,m1)>,假设散列表T中的元素仅为关键字,关键字 k k k等同于包含关键字 k k k的元素。 T T T中的每一个槽或包含一个关键字,或包含 N I L NIL NIL(如果该槽为空)。 H A S H − I N S E R T HASH-INSERT HASHINSERT过程以一个哈希表 T T T和一个关键字 k k k为输入,要么返回其关键字 k k k的存储槽位,要么因为散列表已满而返回出错标志。

HASH-INSERT(T, k)
i=0
repeat
	j=h(k,i)
	if T[j] == NIL
		T[j] = k
		return j
	else i=i+1
until i==m
error “hash table overflow"

查找关键字

  查找关键字k的算法的探查序列与将k插入时的算法一样。因此,在查找的过程中碰到一个空槽时(该位置为NIL),查找算法就停止,因为如果 k k k在表中,它就应该在此处,而不会在探查序列随后的位置上。过程HASH-SEARCH的输入为一个哈希表 T T T和一个关键字 k k k,如果槽 j j j中包含了关键字 k k k,则返回 j j j;如果 k k k不存在 T T T中,则返回 N I L NIL NIL

HASH-SEARCH(T, k)
i=0
repeat
	j=h(k,i)
	if T[j] == NIL
		T[j] = k
		return j
	else i=i+1
until i==m
return NIL

删除关键字

  从开放寻址法的哈希表中删除元素比较麻烦。当我们从槽 i i i中删除关键字时,不能仅仅将其置为 N I L NIL NIL来标识其为空。如果这样做,就会有问题:在插入关键字 k k k时,发现槽 i i i被占用了,则 k k k就被插入到后面的位置上;如果此时将槽 i i i中的关键字删除后,就无法检索到关键字 k k k了。有个解决办法,就是在槽 i i i中置一个特殊的值 D E L E T E D DELETED DELETED替代 N I L NIL NIL来标记该槽。这样就要对过程 H A S H − I N S E R T HASH-INSERT HASHINSERT做相应的修改,将这样的槽当做空槽,是得在此仍然可以插入新的关键字。对 H A S H − S E A R C H HASH-SEARCH HASHSEARCH无需做改动,因为它在搜索时会绕过 D E L E T E D DELETED DELETED标识。但是查找时间就不依赖于装载因子了。
  这里给出的算法参考了Knuth 6.4 Algorithm R(TAOCP),该算法对应使用线性探测来确定关键字 k k k插入位置的HASH-INSERT过程的关键字k删除算法。 H A S H − L I N E A R − P R O B E − D E L E T E HASH-LINEAR-PROBE-DELETE HASHLINEARPROBEDELETE过程以一个哈希表 T T T和一个关键字 k k k为输入。该过程没有使用删除标记 D E L E T E D DELETED DELETED这种方式,而是将关键字 k k k删除后,将该位置之后的关键字做移动这种方式(即重新调整各个关键字的顺序)。
$(i+1) %m $

nextIndex(i,m)
return (i + 1 <= m + 1) ? (i+1) : 1

使用线性探测确定关键字 k k k的索引

HASH-LINEAR-PROBE(T, k)
for i=0 to m-1
	index = (h(k) + i) mod m
	if T[index] == k or T[index] == NIL
		return index
return NIL
HASH-LINEAR-PROBE-DELETE(T, k)
i = HASH-SEARCH(T,k)
if T[i] == NIL
	return
T[i] = NIL
j = i
for i = i+1 to nextIndex(i,m)
	if T[i] == NIL
		return
	r = HASH-LINEAR-PROBE(T, T[i])
	if r == NIL //无法探测到T[i]应该存放的位置
		return
	if i <= r < j or r<j<i or j<i<=r  //r循环地位于i和j之间
		continue
	if r != NIL and r != i //T[i]存放的位置不应该是当前位置
		T[j] = T[i]
		T[i] = NIL
		j = i
		i = i+1

开放寻址法探查序列的计算方法

  有三种技术常用来计算开放寻址法中的探测序列:线性探查、二次探查和双重探查。这几种技术都能保证对每个关键字 k , < h ( k , 0 ) , h ( k , 1 ) , ⋯   , h ( k , m − 1 ) > k,<h(k,0), h(k,1),\cdots, h(k,m-1)> k,<h(k,0),h(k,1),,h(k,m1)>都是 < 0 , 1 , ⋯   , m − 1 > <0, 1,\cdots, m-1> <0,1,,m1>的一个排列。但是,这些技术都不能满足均匀哈希的假设,因为它们能产生的不同探查序列数都不超过 m 2 m^2 m2 个(均匀散列要求有 m ! m! m! 个探查序列)。在三种技术中,双重探查产生的探查序列最多,似乎能给出最好的效果。

  1. 线性探查(linear probing)
      给定一个普通的哈希函数 h ′ : U → 0 , 1 , ⋯   , m − 1 h':U\to{0,1,\cdots,m-1} h:U0,1,,m1,称之为辅助哈希函数,线性探查方法采用的哈希函数为:
    h ( k , i ) = ( h ′ ( k ) + i )    m o d    m ,    i = 0 , 1 , ⋯   , m − 1 h(k,i) = (h'(k) + i) \; mod \;m,\; i=0,1,\cdots,m-1 h(k,i)=(h(k)+i)modm,i=0,1,,m1
    给定一个关键字 k k k,首先探查 T [ h ′ ( k ) ] T[h'(k)] T[h(k)](此时的 i = 0 i=0 i=0),即由辅助哈希函数所给出的槽位。再探查 T [ h ′ ( k ) + 1 ] T[h'(k)+1] T[h(k)+1],以此类推,直至槽 T [ m − 1 ] T[m-1] T[m1]。然后又绕到槽 T [ 0 ] T[0] T[0] T [ 1 ] T[1] T[1] ⋯ \cdots ,直到最后探查到槽 T [ h ′ ( k ) − 1 ] T[h'(k)-1] T[h(k)1]。在线性探查方法中,初始探查位置绝对了整个序列,故只有 m m m种不同的探查序列。
      线性探查法比较容易实现,但它存在着一个问题,称为一次群集。随着连续被占用的槽不断增加,平均查找时间也随之不断增加。群集现象很容易出现,这是因为当一个空槽前有 i i i个满的槽时,该空槽为下一个将被占用的概率是 ( i + 1 ) / m (i+1)/m (i+1)/m。连续被占用的槽就会变得越来越长因而平均查找时间也会越来越大。
  2. 二次探查
      二次探查采用如下形式的哈希函数:
    h ( k , i ) = ( h ′ ( k ) + c 1 i + c 2 i 2 )    m o d    m ,    i = 0 , 1 , ⋯   , m − 1 h(k,i) = (h'(k) + c_1i + c_2i^2) \; mod \;m,\; i=0,1,\cdots,m-1 h(k,i)=(h(k)+c1i+c2i2)modm,i=0,1,,m1
    其中 h ′ h' h是一个辅助哈希函数, c 1 c_1 c1 c 2 c_2 c2为正的辅助常数, i = 0 , 1 , ⋯   , m − 1 i=0,1,\cdots,m-1 i=0,1,,m1。初始的探查位置为 T [ h ′ ( k ) ] T[h'(k)] T[h(k)],后续的探查位置要加上一个偏移量,该偏移量是一个关于 i i i的二次函数。这种探查方法的效果比线性探查好得多,但是,为了能够充分利用哈希表, c 1 c_1 c1 c 2 c_2 c2 m m m的值要受到限制。此外,如果两个关键字的初始探查位置相同,那么它们的探查序列也是相同的,这是因为 h ( k 1 , 0 ) = h ( k 2 , 0 ) h(k_1,0)=h(k_2,0) h(k1,0)=h(k2,0)蕴含着 h ( k 1 , i ) = h ( k 2 , i ) h(k_1,i)=h(k_2,i) h(k1,i)=h(k2,i)。像线性探查一样,初始探测位置决定了整个序列,这样也仅有 m m m个不同的探查序列。
  3. 双重哈希
      双重哈希是用于开放寻址法的最好方法之一,因为它所产生的排列具有随机选择排列的许多特性。双重哈希采用如下的哈希函数:
    h ( k , i ) = ( h 1 ( k ) + i h 2 ( k ) )    m o d    m ,    i = 0 , 1 , ⋯   , m − 1 h(k,i) = (h_1(k) + ih_2(k)) \; mod \;m,\; i=0,1,\cdots,m-1 h(k,i)=(h1(k)+ih2(k))modm,i=0,1,,m1
    其中 h 1 h_1 h1 h 2 h_2 h2均为辅助哈希函数。初始探查位置为 T [ h 1 ( k ) ] T[h_1(k)] T[h1(k)],后续的探查位置加上偏移量 h 2 h_2 h2。因此,不像线程探查或者二次探查,这里的探查序列以两种不同方式依赖于关键字 k k k,因为初始探查位置、偏移量或者二者都可能发生变化。
      为了能查找整个哈希表,值 h 2 ( k ) h_2(k) h2(k)必须要与表的大小 m m m互素(习题11.4-4)。有一种简便的方法确保这个条件成立,就是 m m m为2的幂,并设计一个总产生奇数的 h 2 h_2 h2。另一种方法是取m为素数,并设计一个总是返回较 m m m小的正整数的函数 h 2 h_2 h2
      一个结论:如果对某个关键字 k k k m m m h 2 ( k ) h_2(k) h2(k)有最大公约数 d ≥ 1 d\geq1 d1,则在对关键字 k k k的一次不成功查找中,在返回槽 h 1 ( k ) h_1(k) h1(k)之前,要检查散列表中 1 / d 1/d 1/d个元素。于是,当 d = 1 d=1 d=1时, m m m h 2 ( k ) h_2(k) h2(k)互素,查找操作可能要检查整个哈希表。
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值