第10章 散列表
- 散列表是实现字典操作的一种有效数据结构。
10.1 直接寻址表
- 直接寻址表:表示动态集合,记作 T [ 0.. m − 1 ] T[0..m-1] T[0..m−1]。其中每个位置称作槽。
- 槽:槽k指向集合中一个关键字为k的元素。若没有关键字为k的元素,则 T ( k ) = N I L T(k)=NIL T(k)=NIL。
- 缺点:全域U很大,存储不现实。并且浪费空间。
10.2 散列表
- 利用散列函数h,由关键字k计算出槽的位置。函数h将关键字的全域U映射到散列表 T [ 0.. m − 1 ] T[0..m-1] T[0..m−1]的槽位上: h : U → { 0 , 1 , ⋅ ⋅ ⋅ , m − 1 } h:U\rightarrow \{0,1,···,m-1\} h:U→{0,1,⋅⋅⋅,m−1}。 h ( k ) h(k) h(k)即为关键字k的散列表。
- python提供的散列表为字典
- 冲突:两个关键字可能映射到同一个槽中。
- 解决方法:
- 使散列函数h尽可能的随机,以避免冲突或者使冲突的次数减少
- 链接法
- 开放寻址法
- 解决方法:
- 通过链接法解决冲突:把散列到同一槽中所有元素都放在一个链表中。槽 j j j中有一个指针,它指向存储所有散列到 j j j的元素的链表的表头。
10.3 散列函数
-
好的散列函数的特点:每个关键字都被等可能地散列到m个槽位中任何一个。
-
构造好的散列函数:
- 启发式方法:用除法进行散列和用乘法进行散列。
- 全域散列
-
将关键字转换为自然数:如果所给关键字不是自然数,则必须有一种方法来将它们解释为自然数。
-
除法散列表:通过取k除以m的余数,将关键字k映射到m个槽中的某一个上
- 散列函数: h ( k ) = k m o d m h(k)=k\ mod\ m h(k)=k mod m
-
乘法散列表:
- 步骤:
- 用关键字k乘上常数A(0<A<1),并提取kA的小数部分。
- 用m乘以这个值,再向下取整
- 散列函数: h ( k ) = ⌊ m ( k A m o d 1 ) ⌋ h(k)=\lfloor m(kA\mod 1)\rfloor h(k)=⌊m(kAmod1)⌋
- 步骤:
-
全域散列法:从一组精心设计的函数中,随机地选择散列函数
10.4 开放寻址法
-
开放寻址法:每个表项或包含动态集合的一个元素,或包括NIL。当查找某个元素时,要系统地检查所有的表项,直到找到所需的元素,或最终查明该元素不在表中,这一操作称为探查。
-
伪代码:
-
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"
-
HASH-SEARCH(T, k):查找
i = 0 repeat j = h(k, j) if T[j] == k return j i = i + 1 until T[j] == NIL or i == m return NIL
-
python代码:
## 除法散列表 def h(k, m): return k % m ## 插入 def hash_insert(T, k): m = len(T) i = 1 while i < m: j = h(k, i) if T[j] == None: T[j] = k return j else: i = i + 1 return "hash table overflow" ## 搜索 def hash_search(T, k): m = len(T) i = 1 j = h(k, i) while i < m and T[j] != None: j = h(k, i) if T[j] == k: return j else: i = i + 1 return None T = [None]*10 ## 初始化散列表 for k in [79,69,98,72,14,50]: print(hash_insert(T, k)) for k in [79,98,14,900]: print(hash_search(T, k))
-
-
计算开放寻址法中的探查序列
- 线性探查:给定一个散列函数
h
′
:
U
→
{
0
,
1
,
⋅
⋅
⋅
,
m
−
1
}
h':U\rightarrow \{0,1,···,m-1\}
h′:U→{0,1,⋅⋅⋅,m−1},称之为辅助散列函数
- 散列函数: 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,···,m-1 h(k,i)=(h′(k)+i) mod m, i=0,1,⋅⋅⋅,m−1
- 缺点:一次群集问题,即随着时间的推移,连续被占用的槽不断增加,平均查找时间也随着不断增加。
- 二次探查:
- 散列函数: 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,···,m-1 h(k,i)=(h′(k)+c1i+c2i2) mod m, i=0,1,⋅⋅⋅,m−1
- 初始的探查位置是 T [ h ′ ( k ) ] T[h'(k)] T[h′(k)]
- 如果两个关键字的初始探查位置相同,那么它们的探查序列也是相同的,这一性质导致一种程度较轻的群集现象,称为二次群集。
- 双重散列
- 散列函数: h ( k , i ) = ( h ( k ) + i h 2 ( k ) ) m o d m , i = 0 , 1 , ⋅ ⋅ ⋅ , m − 1 h(k,i)=(h(k)+ih_2(k))\ mod\ m,\ i=0,1,···,m-1 h(k,i)=(h(k)+ih2(k)) mod m, i=0,1,⋅⋅⋅,m−1
- 初始的探查位置是 T [ h 1 ( k ) ] T[h_1(k)] T[h1(k)]
- 线性探查:给定一个散列函数
h
′
:
U
→
{
0
,
1
,
⋅
⋅
⋅
,
m
−
1
}
h':U\rightarrow \{0,1,···,m-1\}
h′:U→{0,1,⋅⋅⋅,m−1},称之为辅助散列函数