07算法导论-散列表(hash table)

前言

很多的应用都需要一些动态集合结构,这些动态集合结构都支持INSERT, SEARCH, DELETE的字典操作。对于普通的数组进行寻址我们需要 Θ ( 1 ) \Theta(1) Θ(1)。但是我们实际存储的关键字数比全部的关键字要小很多,我们使用hash替代普通的数组,在合理假设的情况下,我们做上述操作的时候,也只需要 Θ ( 1 ) \Theta(1) Θ(1)。即使hash函数在最糟糕的情况下,也就是hash都碰撞到同一个槽,这样所需要 Θ ( n ) \Theta(n) Θ(n)
本文首先介绍了hash table是个什么东西,以及内部实现的原理。然后讲解了hash函数,和开放寻址法来处理hash碰撞的问题。最后讲解了完全hash,一种在最坏的情况下,也能在 Θ ( 1 ) \Theta(1) Θ(1)的时间完成search操作。文中有些证明过程就省略了。

hash是什么

hash table原理说明
我们先考虑一下map获取其中元素的方法,就是通过key值来获取的。这里的hash表也是一样的道理。如图所示,我们对输入的key值,使用设计的hash函数来进行计算,得到相应的寻址地址,然后就可以获取到key对应的value。
在讨论hash函数之前,先介绍一下hash碰撞。
假设有 k 1 , k 2 , . . . . . . , k n k_1,k_2,......,k_n k1,k2,......,kn其中 h ( k i ) h(k_i) h(ki)= h ( k j ) h(k_j) h(kj),则就会在hash table里面寻址到同一个位置,这样就会产生hash碰撞。解决hash碰撞的方法就是将slot扩展为链表的结构,slot里面储存这指向链表的头部。这也解释了为什么,在最坏的情况下hash的时间复杂度为n
我们让n个key映射到table上,table有m个slot,在每一个key映射到每一个slot上的概率都是独立和相等的情况下,hash表的承载因子为 α = n / m \alpha=n/m α=n/m
因此搜索一个存在的记录,其给定的时间为 Θ ( 1 + α ) \Theta(1+\alpha) Θ(1+α)

hash函数

hash函数的好坏与否,主要是看能否把keys均匀的分布到table的solt里面。由于我们不知道keys的分布,或者keys可能在一些局部分布会比较密集。因此很难找到一种统一的hash函数的方法。

division method

h ( k ) = k   m o d   m h(k)=k\ mod\ m h(k)=k mod m
m不应为 2 k 2^k 2k,一个不太接近2的整数幂的素数是一个很好的选择。

Multiplication method

h ( k ) = ( A k   m o d   2 w ) r s h ( w − r ) h(k)=(Ak\ mod\ 2^w)rsh(w-r) h(k)=(Ak mod 2w)rsh(wr)
在这里插入图片描述

全域hash

首先需要明确的一点是对于单一的hash函数,我们总能设计相应的key集合,让每一个key都映射到一个solt里面,因此就有了随机选择hash函数的方法也就是全域hash。
我们先定义一个有限的hash函数集合 H H H,hash table的槽位 0 , 1 , . . . . . . . . . . m − 1 {0,1,..........m-1} 0,1,..........m1,对于 k , l ∈ U k,l\in U k,lU。在独立随机分布的情况下,使得 h ( k ) = h ( l ) h(k)=h(l) h(k)=h(l)的hash函数有 ∣ H ∣ / m |H|/m H/m个。这是一个概率问题,对于k,l在随机选择一个hash函数的情况下,他们发生碰撞的概率为 1 / m 1/m 1/m,因此想要使得k,l发生碰撞的函数个数按概率来说需要 ∣ H ∣ / m |H|/m H/m
定理:Let h be a hash function chosen (uniformly) at random from a universal set H of hash function. Suppose h is used to hash n arbitrary keys into the m slots of a table T. Then, for a given key x, we have E [ c o l l i s i o n   w i t h   x ] = n / m E[collision\ with\ x] = n/m E[collision with x]=n/m
上面的定律话句话说就是,从H中随机选取的一个h,使得x发生碰撞的概率不会大于 n m {\frac nm} mn(证明就省略了,也是indicate function)

构造一个全域函数集

构造一个全域hash函数集有很多种方法,现在介绍一种使用点积的构造方法(dot product)。
Let m be prime, Decompose key k into r+1 digits, each with value in the set {0, 1, …, m-1}. That is , let k = { k 0 . . . . . k r } \{k_0.....k_r\} {k0.....kr}, where 0 < = k i < = m 0<=k_i<=m 0<=ki<=m,
randomized strategy:
Pick a = { a 0 . . . a r a_0...a_{r} a0...ar}, where each ai is chosen randomly from {0, 1, … , m-1}
we define h a ( k ) = ∑ i r ( k i ∗ a i ) m o d   x h_a(k)=\sum_i^r{(k_i*a_i)}mod\ x ha(k)=ir(kiai)mod x
所以 ∣ H ∣ = m r + 1 |H|=m^{r+1} H=mr+1
要证明全域hash,即使要证明造成hash碰撞的可能性非常低,低到可以忽略不记。k分解为 { k 0 . . . k r } \{k_0...k_r\} {k0...kr}恰恰方便证明全域hash函数,
证明过程就不给出了,结论是这样的 a 0 = ( ( − ∑ i = 1 r a i ( x i − y i ) ) ∗ ( x 0 − y 0 ) − 1 ) m o d   m a_0=((-\sum_{i=1}^r{a_i(x_i-y_i)})*(x_0-y_0)^{-1})mod\ m a0=((i=1rai(xiyi))(x0y0)1)mod m,
要想造成一次hash碰撞需要试 m r m^r mr次也就是上述说的 ∣ H ∣ m {\frac {|H|}m} mH

开放寻址(open address)

开放寻址和链接法的差别是,它的slot里面没有存放指针,如果hash到一个slot之后,这个槽以及被占有,就使用探查probe方法(线性探查,二次探查,双重探查)进行下一个slot的检索。
至于使用怎样的一个序列来探查slot,我们将这个探查序列表示为 < h ( k , 0 ) , . . . . . k ( k , m − 1 ) > <h(k,0),.....k(k,m-1)> <h(k,0),.....k(k,m1)> < 0 , 1 , . . . , m − 1 > <0,1,...,m-1> <0,1,...,m1>的一个序列。
我们来看看insert

hashInsert(T, k)
	i=0
	repeat
		j = h(k,i)
		if T[j]==nil
			T[i}=k
			return i
		else i++
	until i==m
	error "hash table overflow"

0到m-1有m!种组合,因此探查序列应该是从这m!个序列中选择一个。
线性探查
h ( k , i ) = ( h ‘ ( k ) + i ) m o d   m ,   i = 0 , . . . . , m − 1 h(k,i)=(h^`(k)+i)mod\ m,\ i=0,....,m-1 h(k,i)=(h(k)+i)mod m, i=0,....,m1 为什么mod m这样可以实现循环的探查,但是这个循环只实现一次。
二次探查
h ( k , i ) = ( h ‘ ( k ) + c 1 i + c 2 i 2 ) m o d   m h(k,i)=(h^`(k)+c_1 i+c_2 i^2)mod\ m h(k,i)=(h(k)+c1i+c2i2)mod m不做评论,和线性探查差不多,都有m种可能,由初始位置决定探查序列
双重探查
h ( k , i ) = ( h 1 ( k ) + i ∗ h 2 ( k ) ) m o d   m h(k,i)=(h_1(k)+i*h_2(k))mod\ m h(k,i)=(h1(k)+ih2(k))mod m
这两个函数的设计有很多讲究的,会产生 m 2 m^2 m2种序列,是这三种方法中最好的

完全hash(prefect hash)

首先要明确一点就是完全hash函数是针对静态的key集合,我们可以通过尝试多组hash函数来确定最终的函数完成key到slot的映射。最后实现在 Θ ( 1 ) \Theta(1) Θ(1)的时间和 O ( n ) \Omicron(n) O(n)的空间的hash函数。
我们有定理,随机从全域hash函数中选择一个h,对于 m = n 2 m=n^2 m=n2个槽下映射,发生碰撞的概率不大于1\2。我们可以试几次,就可以得到一个不发生碰撞的函数函数。 但是对于n的平方的空间消耗还是太大了,因此就引入了二级hash的方式,在两级hash里面都使用全域hash函数。第一级的slot的总数为m=n,第二级就有点讲究了为 m j = n j 2 m_j=n_j^2 mj=nj2。我们可以证明其期望的消耗的空间是小于2n的。所以多试几次就好了,稳住

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值