11.1 直接寻址表
练习
11.1-1 考虑由一个长度为m的直接寻址表T表示的动态集合S。给出一个查找S的最大元素的算法过程。所给的过程在最坏情况下的运行时间是什么?
如果不排序的话,最坏时间复杂度是O(m),用快排降序排列,最坏情况是O(lgm)。
11.1-2 位向量(bit vector)是一种仅包含0和1的数组。长度为m的位向量所占空间要比包含m个指针的数组少得多。请说明如何用一个位向量来表示一个包含不同元素(无卫星数据) 的动态集合。字典操作的运行时间应是O(1)。
(“Satellite data”指的是与主数据相关的附加信息或元数据。在数据结构中,卫星数据通常是指存储在主要数据之外的额外信息。这些数据可能用于提供上下文、描述或补充主要数据,而在某些情况下,可以仅关注主要数据本身,不需要这些附加信息。)
非常简单,用连续数组就行了。插入操作设置为1,删除操作设置为0,查找返回是否等于1。
11.1-3 说明如何实现一个直接寻址表,使各元素的关键字不必都不相同,且各元素可以有卫星数据。所有三种字典操作(INSERT,DELETE和 SEARCH)的时间应为O(1)。(不要忘记 DELETE指向要删除的对象的指针变量,而不是关键字。)
如果不考虑卫星数据,那么直接覆盖就行了,三种操作时间复杂度都是O(1)。如果要保留关键字相同但是卫星数据不同的元素,那么使用链表法,但是复杂度变大:
- INSERT: 时间复杂度为 O(1)O(1),因为在链表头插入节点。
- SEARCH: 时间复杂度为 O(n)O(n),其中 nn 是链表中节点的数量,需遍历整个链表以获取所有数据。
- DELETE: 时间复杂度为 O(n)O(n),最坏情况下需要遍历链表寻找要删除的节点。
*11.1-4 我们希望通过利用在一个非常大的数组上直接寻址的方式来实现字典。开始时,该数组中可能包含废料,但要对整个数组进行初始化是不实际的,因为该组的规模太大。请给出在大数组上实现直接寻址字典的方案。每个存储的对象占用 O(1)空间;操作SEARCH,INSERT和 DELETE的时间为 O(1);对数据结构初始化的时间为 O(1)。(提示:可以利用另外一个栈,其大小等于实际存储在字典中的关键字数目,以帮助确定大型数组中某个给定的项是否是有效的。)
假设一个项key,查找进行的操作就是,首先进行 value = hash(key),此value就是此key在Array的位置,根据此索引得到一个值index = Array[value],再根据此index在St中寻找,如果栈中有此索引,那么这个值就存在,且St(index) == key,总的来说就是 St[Array[hash[key]]] == key;
The downside of direct addressing is obvious: if the universe U is large, storinga table T of size j U j may be impractical, or even impossible, given the memoryavailable on a typical computer. Furthermore, the set K of keys actually storedmay be so small relative to U that most of the space allocated for T would bewasted.When the set K of keys stored in a dictionary is much smaller than the universe U of all possible keys, a hash table requires much less storage than a directaddress table. Specifically, we can reduce the storage requirement to ‚. j K j / whilewe maintain the benefit that searching for an element in the hash table still requiresonly O.1/ time. The catch is that this bound is for the average-case time , whereasfor direct addressing it holds for the worst-case time .With direct addressing, an element with key k is stored in slot k . With hashing,this element is stored in slot h.k/ ; that is, we use a hash function h to compute theslot from the key k . Here, h maps the universe U of keys into the slots of a hashtable T [0. . m-1] :h : U -> {0, 1, ... , m-1},where the size m of the hash table is typically much less than j U j . We say that anelement with key k hashes to slot h.k/ ; we also say that h.k/ is the hash value ofkey k . Figure 11.2 illustrates the basic idea. The hash function reduces the rangeof array indices and hence the size of the array. Instead of a size of j U j , the arraycan have size m .
这其实是模拟散列表了,不是单纯的直接寻址。
另一种做法:
增加两个动态数组,S和S',类似于栈,初始化时都为空,当某个关键字变成有效时,压入数组,其最大大小为实际存储在字典中的关键字数目。记直接寻址表为T,当某个关键字k变有效时,T[k]里存储S和S'的索引i,即T[K]=i;S[i]里存储那个有效的关键字k,即S[i]=k;S'[i]里存储指向卫星数据的指针。
11.2 散列表
练习题
在简单均匀散列的情况下,假设我们有n个不同的关键字,将它们散列到一个长度为m的数组T中。每个关键字被散列到m个槽中的任意一个槽上是等概率的。
令C表示冲突的数量,即有两个不同的关键字散列到相同的槽中。我们可以使用指示器随机变量来表示冲突情况。定义指示器变量I_{kl},如果关键字k和关键字l发生冲突(即h(k) = h(l)),则I_{kl} = 1,否则为0。
我们可以写出冲突数C的表达式:
C=求和I_kl (1<=k<l<=n)
期望的冲突数E[C] 是指冲突数C的期望值。由于期望是线性的,我们可以将期望移到求和符号中:
E[C]=E[求和I_kl (1<=k<l<=n)]
然后,我们可以使用简单均匀散列的假设,即每对关键字发生冲突的概率是相同的:
E[C]=求和E[I_kl] (1<=k<l<=n)
因为每对关键字发生冲突的概率相同,所以我们只需要计算一对关键字的冲突概率,并乘以所有可能的一对关键字的数量。有n*(n-1)/2 种可能的一对关键字,所以:
E[C] = n*(n-1)/2 * E[I_12]
现在,考虑任意一对关键字k和l,它们发生冲突的概率是散列函数将它们映射到相同槽的概率。由于采用简单均匀散列,槽的选择是均匀的,所以任意一对关键字发生冲突的概率是 1/m。因此:
E[C]=n*(n-1)/2 * (1/m)
这就是期望的冲突数。