11.1-1 假设一动态集合S用一个长度为m的直接寻址表T来表示。请给出一个查找S中最大元素的过程。你所给的过程在最坏情况下的运行时间是多少?
遍历寻址表T的每个非NIL记号的槽。运行时间O(m)。
11.1-2 位向量(bit vector)是一种仅包含0和1的数组。长度为m的位向量所占空间要比包含m个指针的数组少得多。请说明如何用一个位向量来表示一个包含不同元素(无卫星数据)的动态集合。字典操作的运行时间应是O(1)。
前提是没有卫星数据,那么在一个位向量中,如果k在动态集合内,那么对应位置置1,否则置0。字典操作的运行时间均为O(1)。
11.1-3 试说明如何实现一个直接寻址表,表中各元素的关键字不必都不相同,且各元索可以有卫星数据。所有三种字典操作( INSERT、DELETE 和 SEARCH )的时间应为O(1)。(不要忘记 DELETE 要处理的是被刪除的对象的指针变量,而不是关键字。)
可以让表中的每个槽都存储一个指向包含所有具有该键的对象的双向链表的指针,或者如果没有则为NIL。 查找操作仅返回链表中与给定键相对应的第一个元素。 由于链表中的所有元素都具有相同的键,因此返回哪个元素都无关紧要。 插入只是添加到双向链表的开头。 最后,双向链表的删除操作可以在常数时间内完成。
*11.1-4 我们希望通过利用在一个非常大 的数组上直接寻址的方式来实现一个字典。开始时,该数组中可能包含一些无用信息,但要对整个数组进行初始化是不实际的。因为该组的规模太大。请给出在大数组上实现直接寻址字典的方案。每个存储的对象占用O(1)空间;SEARCH、INSERT 和 DELETE 的时间为O(1),并且对数据结构初始化的时间为 O(1)。(提示:可以利用一个附加数组,处理方式类似于栈,其大小等于实际存储在字典中的关键字数目,以帮助确定大数组中某个给定的项是否是有效的。)
设“非常大 的数组”为A ,附加的数据结构是一个双向链表S(它在许多方面的行为都将像栈一样)。 最初,将S设置为空,并且不执行任何操作来初始化A。 存储在A中的每个对象将包含两个部分:键值和指向S元素的指针,该元素包含指向A中的对象的指针。 当插入x时,先将元素y添加到S中,该元素y包含指向A中x位置的指针。 更新A中的槽A[x]以包含指向S中y的指针。当查找x时,前往A[x]并转到存储在其中的地址。 如果该地址是S的元素,其中包含指向A[x]的指针,则我们知道xA中。否则,xA。当删除x时,删除A[x]指向的S元素。每个字典操作的运行时间为O(1),S中的元素最多与A中的有效元素一样多。
11.2-1 假设用一个散列函数 h 将 n 个不同的关键字散列到一个长度为 m 的数组 T 中。假设采用的是简单均匀散列,那么期望的冲突数是多少?更准确地,集合 { { k, l } :k ≠ l,且 h( k ) ≠ h( l ) } 基的期望值是多少?
设 表示插入到 T 中的第 i 个元素,i = 1 , 2 , ⋯ , n;。
定义指示器随机变量对应关键字。
基于简单均匀散列假设,。
第个元素发生碰撞的期望即为,在之后插入的可能发生的碰撞,即。
对于所有元素发生碰撞数的期望为,。
11.2-2 对于一个用链接法解决冲突的散列表,说明将关键字 5,28,19,15,20,33,12,17,10 插入到该表中的过程。设该表中有 9 个槽位,并设其散列函数为 h( k ) = k mod 9。
11.2-3 Marley 教授作了这样一个假设,即如果将链模式改动一下,使得每个链表都能保持已排好序的顺序,散列的性能就可以有较大的提高。Marley 教授的改动对成功查找、不成功查找、插入和删除操作的运行时间有何影响?
对于成功查找和不成功查找,运行时间会提升至。对于插入和删除没有影响。
11.2-4 说明在散列表内部,如何通过将所有未占用的槽位链接成一个自由链表,来分配和释放元素所占的存储空间。假定一个槽位可以存储一个标志、一个元素加上一个或两个指针。所有的字典和自由链表操作均应具有O( 1 )的期望运行时间。该自由链表需要是双向链表吗?或者,是不是单链表就足够了呢?
struct slot
{
int key; //被占用时:值 未被占用时:无意义(-1)
bool flag; //被占用时:1 未被占用时:0
int pre; //被占用时:具有相同哈希值的上一个元素 未被占用时:自由链表中上一个槽
int next; //被占用时:具有相同哈希值的下一个元素 未被占用时:自由链表中下一个槽
};
1.对于查找没有影响,所以依旧是
(1)x所属的slot未被占用,即不存在与x哈希值相同的关键字,当然也不存在x了
(2)x所属的slot被占用了,但它所存的关键字不属于这个slot,与(1)相同,不存在与x哈希值相同的关键字
(3)x所属的slot被占用了,且它所存的关键属于这个slot,即存在与x哈希值的关键字,只是不知这个关键字是不是x,需要进一步查找
2.对于插入操作:
(1)x所属的slot未被占用,则
- step1:把这个slot从自由链表中移出
- step2:填入关键字x
- step3:修改指针,在这种情况下其next和pre都置为-1
(2)x所属的slot已经被占用,令占用这个slot的关键字是y,y也属于这个slot,则
- step1:从自由链表中取出一个空闲的slot,这个slot肯定不是x所属的slot,只是拿过来用
- step2:填入关键字x
- step3:修改指针,把slot链表入到“以x所属的slot为头结点的队列”中
(3)x所属的slot已经被占用,令占用这个slot的关键字是y,y不属于这个slot,通过(2)可知,这个情况是有可能的
- step1:从自由链表中取出一个空闲的slot,这个slot肯定不是x所属的slot,也不是y所属的slot,只是拿过来用
- step2:在新slot中填入关键字y,修改指针,让y使用这个新slot,而把原来的slot空出来还给x
- step3:在x所属的slot中填入关键字x
- step4:修改“x所属的slot”指针,类似(1)-step3
3.对于删除操作:
(1)x所占用的slot正是x所属的slot,且slot->next=-1,即所有关键字中只有x属于这个slot,x被删除后,slot就空闲了
- step1:释放slot到自由链表中
(2)x所占用的slot正是x所属的slot,但还有别的哈希值相同的关键字占用了临时的slot,此时应该优先释放这些被临时占用的slot
- step1:从以slot为头结点的队列中另选一个slot2,slot2的关键字属于slot而不属于slot2,只是因为slot被占用,所以才用slot2
- step2:把slot2的内容填入slot
- step3:修改指针,让slot代替slot2存在于队列中,不同的是slot还是队列头
- step4:释放slot2到自由链表中
(3)x所占用的slot不是x所属的slot,这个种情况下,这个slot一定不是队列头,还有别的关键字存在于队列中,并且占用了x所属的slot
- step1:把x所占用的slot从“以x所属的slot为头的队列”中移出
- step2:释放slot到自由链表中
插入和删除操作涉及删除一定数量的元素,由于从单链表中删除一定数量的元素的时间不是,所以自由链表需为双向链表。
11.2-5 假设将一个具有n个关键字的集合存储到一个大小为m的散列表中。试说明如果这些关键字均源于全域U,且|U|>nm,则U中还有一个大小为n的子集,其由散列到同一槽位中的所有关键字构成,使得链接法散列的查找时间最坏情况下为。
因为|U|>nm,则对于任意一个槽必有大于等于n个元素被分配到。因此查找时间最坏情况下为。
11.2-6 假设将n个关键字存储到一个大小为m且通过链接法解决冲突的散列表中,同时已知每条链的长度,包括其中最长链的长度L,请描述从散列表的所有关键字中均匀随机地选择某一元素并在的期望运行时间内返回该关键字的过程。
分为两部分:均匀随机选择、返回该关键字。
每个槽内的元素数量的期望为。
一次成功选取的概率为,根据公式,需要尝试次数的期望为。
从散列表中返回一个已知元素需要。
总的期望时间 = 。
11.3-1 假设我们希望查找一个长度为n的链表,其中每一个元素都包含一个关键字 k 并具有散列值 h( k )。每一个关键字都是长字符串。那么在表中查找具有给定关键字的元素时,如何利用各元素的散列值呢?
通过判断散列值是否与链表中某一节点相同来查找元素。
11.3-2 假设将一个长度为 r 的字符串散列到 m 个槽中,并将其视为一个以128为基数的数,要求应用除法散列法。我们可以很容易地把数 m 表示为一个32位的机器字,但对长度为 r 的字符串,由于它被当做以128为基数的数来处理,就要占用若干个机器字。假设应用散列法计算一个字符串的散列值,那么如何才能在除了该串本身占用的空间外,只利用常数个机器字。
题目解析:一个字符串如果以2为基数,比如“mystr”,可以转化为ascii(m)*+ascii(y)*+ascii(s)*+ascii(t)*2+ascii(r)*,但如果以128为基数,转化出来的数字就会很大,要占用很多个机器字。
11.3-3 考虑除法散列法的另一种,其中h( k ) = k mod m,m = -1,k 为按基数 表示字符串。试证明,如果串 x 可由串 y 通过其自身的字符置换排列导出,则 x 和 y 具有相同的散列值。给出一个应用的例子,其中这一特性在散列函数中是不希望出现的。
设 x,y 均由 s[0],s[1] ... s[n] 所表示的字符表示。
11.3-4 考虑一个大小为 m = 1000 的散列表和一个对应的散列函数 ,其中 A = (√5-1)/2,试计算关键字61,62,63,64和65被映射到位置。
h( 61 ) = 700
h( 62 ) = 318
h( 63 ) = 936
h( 64 ) = 554
h( 65 ) = 172
*11.3-5 定义一个从有限集合U到有限集合B上的散列函数簇ℋ为全域的,如果对U中所有的不同元素对k和l,都有
其中概率是相对从函数簇ℋ中随机抽取的散列函数h而言的。试证明:一个全域的散列函数簇必定满足:
设,表示槽中的元素数。
如果一个哈希函数能够将U中的个元素映射到B中b个槽中的每一个槽中,那么我们得到的总的碰撞数是最小的。-->
- 任意一个槽中的碰撞次数:
- 总碰撞次数:
- = 总碰撞次数 / 集合U中每一对不同元素构成的元素对的碰撞数
*11.3-6 设U为取自中的值构成的元素集合,并设B=,其中p为素数。对于一个取自U的输入n元组,定义其上的散列函数为
并且设。根据练习11.3-5中全域的定义,证明ℋ是全域的。
在上式中,对一组确定的n元组,具有相同余数的b最多有(n-1)个,即ℋ中最多有 (n - 1) 个散列函数会导致两个不同输入散列结果相同,因此。根据定义,ℋ是全域的。
11.4-1 考虑将关键字10、22、31、4、15、28、17、88、59用开放寻址法插入到一个长度为m=11的散列表中,辅助散列函数为h(k)=k mod m。说明用线性探查、二次探查(c1=1,c2=3)以及双重散列h2(k)=1+(k mod (m-1))将这些关键字插入散列表的结果。
线性探查:
二次探查:
双重散列:22, NIL, 59, 17, 4, 15, 28, 88, NIL, 31, 10
11.4-2 请写出HASH-DELETE的伪代码;修改HASH-INSERT,使之能处理特殊值DELETED。
HASH-DELETE(T,k)
i = HASH − SEARCH(T; k)
T[i] = DELETED
HASH-INSERT(T,k)
i=0
repeat
j=h(k,i)
if T[j]==NIL or T[j]==DELETED
T[j]=k
return j
else i++
until i==m
error "hash table overflows"
11.4-3 考虑一个采用了均匀散列的开放寻址散列表。给出当装载因子为3/4和7/8时,在一次不成功查找中期望探查数的上界,以及一次成功查找中期望探查数的上界。
套用定理6和定理8
时:不成功查找=4;成功查找=
时:不成功查找=8,成功查找=
*11.4-4 假设采用双重散列来解决冲突;即所用的散列函数为 h(k, i) = ( h1( k ) + h2( k ) ) mod m。试证明:如果对某个关键字k,m 和 h2( k ) 有最大公约数 d ≥ 1,则在对关键字k的一次不成功的查找中,在回到槽h1(k)之前,要检查散列表中第(m/d)个元素。于是,当d = 1时,m与h2(k)互质,查找操作可能要检查整个散列表。(提示:见第31章)
对于两个互素的数a,b,即gcm(a, b) = 1,有 a*x ≡ a*(by + x) mod b,其中x = 0,1 ... b-1。
例:4和5互素,gcm(4, 5) = 1
4*0 mod 5 = 0
4*1 mod 5 = 4
4*2 mod 5 = 3
4*3 mod 5 = 2
4*4 mod 5 = 1
4*5 mod 5 = 0
∴若gcm(h2(k), m) = d,则gcm(h2(k)/d, m/d) = 1,则h2( k ) mod m 循环一次需要经过( m/d )个数。
∴当d = 1时,循环一次需要 m 个数,即双重散列表查找需要检查整个散列表。
*11.4-5 考虑一个装载因子为 的开放寻址散列表。找出一个非零的值,使得在一次不成功的查找中,期望的探查数等于成功查找中期望探查数的 2 倍。这两个探查期望数可以使用定理 11.6 和定理 11.8 中给定的上界。
*11.5-1 假设采用了开放寻址法和均匀散列计数将n个关键字插入到一个大小为m的散列表中。设p ( n , m )为没有冲突发生的概率。试证明:p ( n , m ) ≤ 。(提示:见式(3.12)。)论证当n 超过时,不发生冲突的概率快速趋于0。