散列表
散列表是实现字典操作的一种有效数据结构。散列表是普通数组概念的推广,由于对普通数组可以直接寻址,使得能在O(1)时间内访问数组中的任意位置。如果存储空间允许,我们可以提供一个数组,为每个可能的关键字保留一个位置,以利用直接寻址技术的优势。当实际存储的关键字数目比全部的可能关键字总数要小时,采用散列表就成为直接数据寻址的一种有效替代,因为散列表使用一个长度与实际存储的关键字数目成比例的数据来存储。在散列表中,不是直接把关键字作为数组的下标,而是根据关键字计算出相应的下标。
11.1 直接寻址表
直接寻址表(direct address table)就是数组。
DIRECT-ADDRESS-SEARCH(T, k)
return T[k]
DIRECT-ADDRESS-INSERT(T, x)
T[x, key]=x
DIRECT-ADDRESS-DELETE(T, x)
T[x, key]=null
上述的每一个操作都只需要O(1)的时间。
对于某些应用,直接寻址表本身就可以存放动态集合中的元素。也就是说,并不把每个元素的关键字及其卫星数据都放在直接寻址表外部的一个对象中,再由表中某个槽的指针指向该对象,而是直接把该对象存放在表的槽中,从而节省了空间。我们使用对象内的一个特殊关键字来表明该槽为空值。
11.2 散列表
直接寻址技术的缺点是非常明显的:如果全域U很大,则在一台标准的计算机可用内存容量中,要存储大小为|U|的一张表T也许不太实际,甚至是不可能的。还有,实际存储的关键字集合K相对U来说可能很小,使得分配给T的大部分空间都被浪费掉。这个时候,散列表需要的存储空间比直接寻址表要少得多。
在直接寻址方式下,具有关键字k的元素被存放在槽k中,在散列方式下,该元素存放在槽h(k)中,即利用散列函数(hash function)h,由关键字k计算出槽的位置。
两个关键字映射到同一个槽中称为冲突。但我们可以找到有效的方法来解决冲突。
通过链接法解决冲突
在链接法中,把散列到同一槽中的所有元素都放在一个链表中。槽j中有一个指针,它指向存储所有散列到j的元素的链表的表头,如果不存在这样的元素,则槽j中为null。
CHAIN-HASH-INSERT(T, x)
insert x at the head of list T[h(x, key)]
CHAIN-HASH-SEARCH(T, k)
search for an element with key k in list T[h(k)]
CHAIN-HASH-DELETE(T, x)
delete x from the list T[h(x, key)]
11.3 散列函数
- 除法散列法
- 乘法散列法
- 全域散列法
11.4 开放寻址法
在开放寻址法(open addressing)中,所有的元素都存放在散列表中。每个表项或包含动态集合的一个元素,或包含null。当查找某个元素时,腰息痛的检查所有的表项,知道找到所需的元素,或者最终查明该元素不在表中。在开放寻址法中,散列表可能会被填满,以至于不能查如任何新的元素。该方法导致的一个结果便是装载因子α绝对不能超过1.
开放寻址法的好处在于它不用指针,而是计算要存取的槽序列。于是,不用存储指针而节省的空间,使得可以用同样的空间来提供更多的槽,潜在的减少了冲突,提高了检索速度。