散列表
直接寻址表
当关键字的全域U比较小时,直接寻址是一种简单而有效的技术。如果储存空间允许,我们可以提供一个数组,为每个可能的关键字保留一个位置,以利用直接寻址的优势。
假设某应用要用到一个动态集合,其中每个元素都是取自全域U={0,1,…,m-1}中的一个关键字,这里m不是一个很大的数。另外,假设没有两个元素具有相同的关键字。
为表示动态集合,我们用一个数组,或称为直接寻址表,记为T[0…m-1].其中每个位置,称为槽,对应全域U中的一个关键字。槽k指向集合中一个关键字为k的元素,如果该集合中没有关键字为k的元素,则T[k]=NIL。
direct-address-search(T,k)
1 return T[k]
direct-address-insert(T,x)
1 T[x.key]=x
direct-address-delete(T,x)
1 T[x.key]=NIL
对于某些应用,直接寻址表本身就可以存放动态集合中的元素。也就是说,并不把每个元素的关键字及其卫星数据都放在直接寻址表外部的一个对象中,在由表中某个槽的指针指向该对象,而是直接把对象该对象存放在表的槽中。而且通常不用储存该对象的关键字属性,因为如果知道一个对象在表中的下标,就可以得到它的关键字。然而,如果不储存关键字,我们就必须有某个方法来确定某个槽是否为空。
散列表
当全域U很大,在标准计算机可用的内存容量中,要储存一张大小为U的表T是不太可能的;或者实际储存的关键字集合K相对U来说很小时,分配给T的大部分空间将会被浪费。此时散列表就成为直接寻址表的一种有效替代,因为散列表使用一个长度与实际储存的关键字数目成比例的数组来储存。
在直接寻址方式下,具有关键字k的元素被存放在槽k中;在散列方式下,该元素储存在槽h(k)中。即利用散列函数h,由关键字k计算出槽的位置。函数h将关键字的全域U映射到散列表T[0…m-1]的槽位上,散列表的大小m一般比U小得多。我们可以说一个具有关键字k的元素被散列到槽h(k)上,也可以说h(k)是关键字k的散列值。
存在的问题:两个关键字可能映射到同一个槽中,我们称之为冲突。
链接法解决冲突
解决冲突应解决以下问题:
1.如何组织在同一个桶中的值?
2.如何解决一个桶中分配太多值情况?
3.如何在特定的桶中搜索目标值?
在链接法中,把散列到同一槽中的所有元素都放在一个链表中。
chained-hash-search(T,x)
1 search for an element with key k in list T[h(k)]
chained-hash-insert(T,x)
1 insert x at the head list T[h(x.key)]
chained-hash-delete(T,x)
delete x from the list T[h(x.key)]
散列函数
除法散列法
通过取k除以m的余数,将关键字k映射到m个槽中的某一个上,即h(k)=k mod m.例如,如果散列表的大小为m=12,所给关键字k=100,则h(k)=4.
当应用除法散列法时,要避免选择m的某些值。例如,m不应为2的幂,否则h(k)就是k的p个最低位数字。一个不太接近2的整数幂的素数,常常是m的一个比较好的选择。
乘法散列法
用关键字k乘上常数A(0<A<1),并提取kA的小数部分,然后用m乘以这个值,在向下取整,即h(k)=
⌊
m
(
k
A
m
o
d
1
)
⌋
\lfloor m(kA mod 1) \rfloor
⌊m(kAmod1)⌋
乘法散列法的一个优点是对m的选择不是特别关键,其中
A
=
(
5
)
−
1
A=\sqrt(5)-1
A=(5)−1