Mit6.006-lecture04-Hashing

一、回顾

数据结构 操作,最坏情形O
容器(container) 静态(static) 动态(dynamic) 顺序(order)
build(X) find(k) insert(x)
delete(k)
find_min()
find_max()
find_prev(k)
find_next(k)
数组 n n n n n
有序数组 nlogn logn n 1 logn

想要更快的查找以及动态操作。我们能让find(k)比 θ ( l o g n ) \theta(logn) θ(logn)更快?

二、比较模型

在本模型中,假定算法仅可以通过比较进行区分

可比较项目:黑盒,仅支持两两比较

比较是:<、<=、>、>=、=、!=,输出是二进制:true或false

目标:存储一组n个可比较项目,支持find(k)操作

三、决策树

任何算法可被视作操作执行的决策树

一个内部节点代表一个二元比较,分叉到true或false

对于比较算法,决策树是二叉的

叶子代表算法终止,导致一个算法输出

根到叶子的路径代表算法对于输入的执行

每个算法的输出至少需要一个叶子,因此查询需要>=n+1个叶子

四、比较查询下边界

什么是比较查询算法最坏情形运行时间

运行时间>= 比较次数>=任意根到叶子的路径的最大长度>=树的高度

≥ \ge n个节点的二叉树的最小高度是多少?

完整二叉树最小高度是多少(除了最后一行,其他行都是满的)

高度 ≥ ⌈ l g ( n + 1 ) ⌉ − 1 = Ω ( l o g n ) , \ge \lceil lg(n+1)\rceil - 1=\Omega(logn), lg(n+1)⌉1=Ω(logn)因此任意比较排序的运行时间是 Ω ( l o g n ) \Omega(logn) Ω(logn)

有序数组实现这个边界

θ ( n ) \theta(n) θ(n)个叶子、最大分支因子b的树的高度为 Ω ( l o g b n ) \Omega(log_bn) Ω(logbn)

为了更快,需要一个操作:允许常量分支因子

五、直接访问数组

利用Word-RAM O ( 1 ) \mathcal{O}(1) O(1)时间随机访问索引,线性分支因子

给项目独一无二的整数key:k,{0,…,k-1},在数组索引k处,存储项目

用一个数组索引关联一个目标

如果key适合机器字, u ≤ 2 w u\le2^w u2w,最坏情形的 O ( 1 ) \mathcal{O}(1) O(1)查找、动态操作

6.006:假设输入数字/字符串符合word,除非长度显示参数化

计算机内存中的任何东西都是二进制整数,要么是64位地址

但空间 O ( u ) \mathcal{O}(u) O(u),如果 n < < u n<n<<u,这也是很坏的

举例:如果key是10字母名称,所有可能的名字, 2 6 10 = 17.6 T B 26^{10}=17.6TB 2610=17.6TB空间

我们如何可以用更少的空间?

六、哈希

如果n< θ ( n ) \theta(n) θ(n),并使用更小的直接访问数组

哈希函数:h(k):{0,…,u-1}->{0,…,m-1}

直接映射数组称为哈希表,h(k)称为k的hash

如果m<

总是存在keys:a和b,h(a)=h(b)->冲突

不能存储两个项目在相同索引,那么存到哪?

要么存到数组其他地方(开放寻址)复杂的分析,常见且实用

存储到其他支持动态集合接口数据结构(链)

七、链表

存储冲突到另外的数据结构(链表)

如果keys分发到索引处,链表尺寸为:n/m=n/ Ω ( n ) \Omega(n) Ω(n)= O ( 1 ) \mathcal{O}(1) O(1)

如果链表有 O ( 1 ) \mathcal{O}(1) O(1)尺寸,所有操作花费 O ( 1 ) \mathcal{O}(1) O(1)时间

如果不是如此,一些项目可能映射到相同位置,h(k)=constant,链尺寸是 θ ( n ) \theta(n) θ(n)

需要好的hash函数!那么什么是好的hash函数

八、哈希函数

除法(bad): h ( k ) = ( k   m o d   m ) h(k)=(k\ mod\ m) h(k)=(k mod m)

当keys均匀分布时很好

m要避免存储keys的相似性

远离2和10的幂的大素数是合理的

python使用它的某个版本,附带一些其他混合

若u>>n,每个hash函数将有一些输入集,它将创建一个 O ( n ) \mathcal{O}(n) O(n)尺寸的链

不使用固定的hash函数,随机地选择一个

universal(good,theoretically): h a b ( k ) = ( ( ( a k + b ) m o d   p ) m o d   m ) h_{ab}(k)=(((ak+b)mod\ p)mod\ m) hab(k)=(((ak+b)mod p)mod m)

Hash Family H ( p , m ) = { h a b ∣ a , b ∈ { 0 , . . . , p − 1 } , a ≠ 0 } H(p,m)=\{h_{ab}|a,b\in\{0,...,p-1\},a\neq0\} H(p,m)={haba,b{0,...,p1},a=0}

固定的素数p>u,a和b从{0,…,p-1}中选出

H是一个universal family: Pr ⁡ h ∈ H { h ( k i ) = h ( k j ) } ≤ 1 / m    ∀ k i ≠ k j ∈ { 0 , . . . , u − 1 } \Pr\limits_{h \in H}\{h(k_i)=h(k_j)\} \le 1/m\ \ \forall k_i\neq k_j \in\{0,...,u-1\} hHPr{h(ki)=h(kj)}1/m  ki=kj{0,...,u1}

为什么universality是有用的?暗含了短链的长度!

X i j X_{ij} Xij标志随机变量 h ∈ H :如果 h ( k i ) = h ( k j ) , X i j = 1 ; 否则 X i j = 0 h\in H:如果h(k_i)=h(k_j),X_{ij}=1;否则X_{ij}=0 hH:如果h(ki)=h(kj),Xij=1;否则Xij=0

h ( k i ) h(k_i) h(ki)处链的长度是随机变量: X i = ∑ j X i j X_i=\sum_jX_{ij} Xi=jXij

链在 h ( k i ) h(k_i) h(ki)处的期望尺寸

E { X i } = E { ∑ j X i j } = ∑ j E { X i j } = 1 + ∑ j ≠ i E { X i j } = 1 + ∑ j ≠ i ( 1 ) P r { h ( k i ) = h ( k j ) } + ( 0 ) P r { h ( k i ) + h ( k j ) } ≤ 1 + ∑ j ≠ i 1 / m = 1 + ( n − 1 ) / m E\{X_i\}=E\{\sum\limits_jX_{ij}\}=\sum\limits_jE\{X_{ij}\}\\=1+\sum\limits_{j\neq i}E\{X_{ij}\}=1+\sum\limits_{j\neq i}(1)Pr\{h(k_i)=h(k_j)\}+(0)Pr\{h(k_i)+h(k_j)\} \\\le1+\sum\limits_{j\neq i}1/m=1+(n-1)/m E{Xi}=E{jXij}=jE{Xij}=1+j=iE{Xij}=1+j=i(1)Pr{h(ki)=h(kj)}+(0)Pr{h(ki)+h(kj)}1+j=i1/m=1+(n1)/m

因为 m = Ω ( n ) ,负载因子 α = n / m = O ( 1 ) ,因此 O ( 1 ) 是符合期望的 m=\Omega(n),负载因子\alpha=n/m=\mathcal{O}(1),因此\mathcal{O}(1)是符合期望的 m=Ω(n),负载因子α=n/m=O(1),因此O(1)是符合期望的

九、动态

如果n/m远大于1,为新尺寸m用新随机选择的哈希函数进行重建(rebuild)

像动态数组那样做同样的分析,花费可以被一些动态操作分摊

因此哈希表能够,以所期望的摊还 O ( 1 ) \mathcal{O}(1) O(1)时间,实现动态集合操作

数据结构 操作,最坏情形O
容器(container) 静态(static) 动态(dynamic) 顺序(order)
build(X) find(k) insert(x)
delete(k)
find_min()
find_max()
find_prev(k)
find_next(k)
数组 n n n n n
有序数组 nlogn logn n 1 logn
直接访问数组 u 1 1 u u
哈希表 n 1 1 n n

你可能感兴趣的:(算法,算法)