第11章《散列表》:基于双重散列的开放寻址法 python实现

散列表

在直接寻址方式中,具有关键字 k k k的元素被存放在槽 k k k中,在散列方式下,该元素存放在槽 h ( k ) h(k) h(k)中,即利用散列函数 h h h,由关键字 k k k计算出槽的位置。函数 h h h将关键字的全域 U U U映射到散列表 T [ 0.. m − 1 ] T[0..m-1] T[0..m1]的槽位上,这里散列表的大小 m m m一般要比 ∣ U ∣ |U| U小的多,下图描述了这个基本方法。
在这里插入图片描述
这里存在一个问题,两个关键字可能映射到同一个槽中,我们称这种情形为冲突。我们可以 h h h尽可能的“随机”,从而避免冲突或者使冲突的次数最小化。

  • 通过链接法解决冲突:把散列到同一槽中的所有元素都放在一个链表中,如下图所示,槽 j j j中有一个指针,它指向存储所有散列到 j j j的元素的链表的表头,如果不存在这样的元素,则槽 j j j中为 N I L NIL NIL
    在这里插入图片描述
  • 链接法散列的分析:散列方法的平均性能依赖于所选取的散列函数 h h h,将所有关键字集合分布在在 m m m个槽位上的均匀程度。给定一个能存放 n n n个元素,具有 m m m个槽位的散列表 T T T,定义 T T T的装载因子 a a a n / m n/m n/m,即一个链的平均存储元素数。
    定理1:在简短均匀散列的假设下,对于用链接法解决冲突的散列表,一次不成功查找的平均时间 Θ ( 1 + a ) \Theta(1+a) Θ(1+a)
    定理2:在简短均匀散列的假设下,对于用链接法解决冲突的散列表,一次成功查找的平均时间 Θ ( 1 + a ) \Theta(1+a) Θ(1+a)

散列函数

有时,我们知道关键字的概率分布,如果各关键字都是随机的实数 k k k,他们独立均匀地分布于 0 ≤ k < 1 0\le k<1 0k<1范围中,那么散列函数 h ( k ) = ⌊ k m ⌋ h(k)=\lfloor km \rfloor h(k)=km
就能满足简单均匀散列的假设条件,好的散列函数应能将这些相近符号散列到相同槽中的可能性最小化,常见的散列函数如下:

  • 除法散列法:通过取 k k k除以 m m m的余数,将关键字 k k k映射到 m m m个槽中的某一个,即散列函数为: h ( k ) = k   m o d   m h(k)=k{\ }mod{\ } m h(k)=k mod m
  • 乘法散列法:用关键字 k k k乘上常数A(0<A<1),提取 k A kA kA的小数部分,再用 m m m乘以这个值,再向下取整,散列函数为: h ( k ) = ⌊ m ( k A   m o d   1 ) ⌋ h(k)= \lfloor m(kA{\ }mod{\ }1) \rfloor h(k)=m(kA mod 1)
  • 全域散列法:随机的选择散列函数,使之独立于要存储的关键字,避免出现最坏的情况,所有的关键字散列到同一个槽中

开放寻址法

在开放寻址法中,所有元素都存放在散列表里,每个表项或包含动态集合的一个元素,或包含 N I L NIL NIL,开放寻址的好处在于它不用指针,而是要计算出要存取的槽序列,于是,不用存储指针而节省的空间,使得可以用同样的空间来提供更多的槽,潜在地减少了冲突,提高了检索速度。

  • 探查序列:为了使用开放寻址法插入一个元素,需要连续地检查散列表,或称为探查,直到找到一个空槽来放置待插入的关键字为止,检查的顺序不一定是0,1,…,m-1,而是依赖待插入的关键字。对每个关键字 k k k,使用开放寻址法的探查序列 < h ( k , 0 ) , h ( k , 1 ) , . . . , h ( k , m − 1 ) > <h(k,0),h(k,1),...,h(k,m-1)> <h(k,0),h(k,1),...,h(k,m1)>是<0,1,…,m-1>的一个排列
  • 探查序列方法:有三种技术常用来计算开放寻址中的探查序列:线性探查、二次探查和双重探查。
    线性探查 h ( k , i ) = ( h ∗ ( k ) + i )   m o d   m h(k,i)=(h^*(k)+i) {\ }mod {\ }m h(k,i)=(h(k)+i) mod m
    二次探查 h ( k , i ) = ( h ∗ ( k ) + c 1 i + c 2 i 2 )   m o d   m h(k,i)=(h^*(k)+c_1i+c_2i^2) {\ }mod {\ }m h(k,i)=(h(k)+c1i+c2i2) mod m
    双重散列 h ( k , i ) = ( h 1 ( k ) + i h 2 ( k ) )   m o d   m h(k,i)=(h_1(k)+ih_2(k)) {\ }mod {\ }m h(k,i)=(h1(k)+ih2(k)) mod m
    其中 h 1 ( k ) h_1(k) h1(k) h 2 ( k ) h_2(k) h2(k)均为辅助散列函数,我们可取 m m m为素数,并取 h 1 ( k ) = k   m o d   m , h 2 ( k ) = 1 + ( k   m o d   m ∗ ) h_1(k)=k{\ }mod{\ }m,h_2(k)=1+(k{\ }mod{\ }m^*) h1(k)=k mod mh2(k)=1+(k mod m),其中 m ∗ m^* m稍小于 m m m(比如 m − 1 m-1 m1),双重散列是用于开放寻址法的最好方法之一,它产生的排列具有随机选择排列的许多性质,如下图所示,双重散列过程如下:
    在这里插入图片描述
    基于双重散列的开放寻址法 python实现:
# -*-coding:utf8 -*-
import sys

class OpenAddrssHash(object):
	def __init__(self,size):
		#初始化散列表A,大小为size
		self.T = [None]*size
		self.size = size
	
	#双重散列表定义
	def double_hash(self, k,i):
		h1 = k % self.size
		h2 = 1 + k%(self.size-2)
		h = (h1 + i*h2) % self.size
		return h

	#向散列表T中插入k,并返回槽位j
	def hash_insert(self, k):
		i = 0
		while i<self.size:
			j = self.double_hash(k, i)
			if self.T[j] == None:
				self.T[j] = k
				return j
			i+=1
		return "hash table overflow"
	#向散列表T中搜索关键词k,返回槽位j
	def hash_search(self, k):
		i = 0
		j = self.double_hash(k,i)
		while self.T[j]!=None and i<self.size:
			j = self.double_hash(k,i)
			if self.T[j]==k:
				return j
			i+=1
		return None

if __name__=='__main__':
	example = OpenAddrssHash(13)
	#插入
	for k in [79,69,98,72,14,50]:
		print(example.hash_insert(k))
	#查找
	for k in [79,98,14,900]:
		print(example.hash_search(k))
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值