8.6总结与提高
一、主要知识点
1. 查找表的检索机制
查找表是由同一类型元素构成的集合。
本章给出了三种类型的查找表:
- 第一类主要基于线性结构,记录关键字一般按序排列,以提高检索速度,主要采用基于比较的顺序检索或折半检索方法。由于这一类查找表主要用于查找,一般不对表做插入和删除操作,通常称为静态查找表。
- 第二类主要基于树形结构,包括二叉树和 m 叉树,主要采用基于比较的分支检索方法,即从树根开始,根据比较结果,沿着特定的分支进行检索,其检索的时间复杂度与树的深度同级别为对数函数。由于这一类查找表不仅用于查找,还需要对表做插入和删除操作,通常称为动态查找表。
- 第三类是散列(哈希)结构,根据数据的关键字“计算” 数据的存储地址。散列(哈希)法既是建立表的方法,也是查找表的方法,其对应的检索方法是“计算式”的检索。
2. 平均查找长度
为确定数据元素在列表中的位置,需和给定值进行比较的关键字个数的期望值,称为查找算法在查找成功时的平均查找长度。由于查找算法的基本运算是关键字之间的比较操作,所以平均查找长度是衡量查找算法的性能的重要指标。
计算平均查找长度的基本公式:
对于长度为 n 的列表,查找成功时的平均查找长度为:
其中 Pi 为查找列表中第 i 个数据元素的概率,Ci 为找到列表中第 i 个数据元素时,已经进行过的关键字比较次数。
计算方法:
针对具体的查找问题,一般直接根据上述定义计算平均查找长度,通常称为手工计算方式。也可采用预先推导出的理论公式进行计算,理论公式一般基于合理的假设,通用但有一定误差。虽然两种方式在计算相同问题时得到的具体结果有一定差异,但都是基于相同的平均查找长度定义,故应当掌握这些方法原则并能应用。
3、 折半查找
折半查找法要求待查找表应采用顺序存储结构且按关键字有序排列。
折半查找过程借助于折半判定树加以描述。判定树中每一结点对应表中一个记录在表中的位置序号。
折半查找算法的性能:在等概率时查找成功的平均查找长度与折半判定树的深度相关。
折半查找算法查找速度快,平均性能好;插入删除较困难。
4、二叉排序树
二叉排序树的定义:树中左子树上所有结点的值均小于根结点的值,树中右子树上所有=结点的值均大于根结点的值。
二叉排序树的构建过程:从空树开始,每次都从根开始比较插入(小于插入左子树,大于插入其右子树),逐一往树中插入序列中所有结点。
二叉排序树的构建形态:含有 n 个相同结点的二叉排序树形态各异,其构造形态与数列的输入顺序有关。
二叉排序树的特性:利用中序遍历的定义与二叉排序树的定义可知,对一个二叉排序树进行中序遍历,可以得到结点值的递增有序序列。
二叉排序树的查找过程:
二叉排序树的查找性能:与折半查找过程类似,在二叉排序树中查找一个记录时,其比较次数不超过树的深度。就平均性能而言,二叉排序树上的查找和折半查找相差不大,平均查找长度仍然是 O(log2n)。
二叉排序树的插入、删除操作无需移动大量结点,经常变化的动态表宜采用二叉排序树结构。
5、平衡二叉排序树
普通二叉排序树中各个分支的高度可能相差悬殊,而平衡二叉排序树中各个分支的高度能够始终保持平衡,从而保证较高的查找效率。
结点的平衡因子是结点的左子树深度与右子树深度之差。在平衡二叉排序树中,任意结点的平衡因子的绝对值小于等于 1。
在平衡二叉排序树上插入一个结点时可能导致失衡,有四种失衡类型及相应的调整方法。
6、哈希法
**基本思想:**以元素的关键字 k 为自变量,通过哈希函数 H,计算其存储位置 p 即 p=H(k),从而实现按关键字计算的方式建立表与查找表。哈希表的查找过程与哈希表的创建过程对应一致。
致。
哈希法主要包括:
- 1)哈希函数构造,
- 2)处理冲突方法。
构造哈希函数常用的方法有除留余数法。处理冲突的基本方法包括线性探测再散列、二次探测再散列、链地址法等。
哈希法中影响关键字比较次数的因素有三个:哈希函数、处理冲突的方法以及哈希表的装填因子。其中设哈希函数是均匀的,并且按处理冲突的方法分别考虑,则影响平均查找长度的因素只剩下装填因子α。哈希表的平均查找长度是装填因子α的函数,而与待散列元素数目 n 无关。无论元素数目 n 有多大,都能通过调整α,使哈希表的平均查找长度较小。
二、典型题解
本章典型题解主要以哈希处理技术为基础,展开平均查找长度的公式计算法、手工计算法、算法实现技术。
【例 1】为 1000 个记录设计哈希表,假设哈希函数是均匀的,解决冲突用线性探测再散列法,并要求在等概率情况下查找成功时的 ASL 不超过 3,查找不成功时的 ASL 不超过 13,则哈希表长度 m 应取多大?
解:本题要求应用公式计算平均查找长度。
已知哈希函数是均匀的,且解决冲突用线性探测再散列法时,在等概率情况下查找成功和查找不成功时的平均查找长度为:
解得α≤0.8,取α=0.8,由于 0.8=1000/m,所以 m=1250
需要指出的是,上述结果是在假设哈希函数均匀的情况下得到的,当我们取定一个具体的哈希函数时,一般不会很均匀,所以实际构造的哈希表的查找性能会稍微差一些。
【例 2】对以下关键字序列建立哈希表:(SUN, MON, TUE, WED, THU, FRI, SAT),哈希函数为 H(K) =(K 中第一个字母在字母表中的序号)MOD 7。用线性探测法处理冲突,要求构造一个装填因子为 0.7 的哈希表,并计算出在等概率情况下查找成功的平均查找长度。
解:此题主要要求掌握手工计算平均查找长度方法。
装填因子为 0.7,根据公式:装填因子=元素个数/表长,知哈希表长度=7/0.7=10。各关键字第一个字母的序号分别为 19(S)、13(M)、20(T)、23(W)、6(F)。
计算各关键字的哈希地址:
H(SUN)=19mod7=5;
H(MON)=13mod7=6;
H(TUE)=20mod7=6 (与 MON 冲突)
H1(TUE)=(6+1)mod10=7;
H(WED)=23mod7=2;
H(THU)=20mod7=6 (与 MON 冲突)
H1(THU)=(6+1)mod10=7 (与 TUE 冲突)
H2(THU)=(6+2)mod10=8;
H(FRI)=6mod7=6 (与 MON 冲突)
H1(FRI)=(6+1)mod10=7 (与 TUE 冲突)
H2(FRI)=(6+2)mod10=8 (与 THU 冲突)
H3(FRI)=(6+3)mod10=9;
H(SAT)=19mod7=5 (与 SUN 冲突)
H1(SAT)=(5+1)mod10=6 (与 MON 冲突)
H2(SAT)=(5+2)mod10=7 (与 TUE 冲突)
H3(SAT)=(5+3)mod10=8 (与 THU 冲突)
H4(SAT)=(5+4)mod10=9 (与 FRI 冲突)
H5(SAT)=(5+5)mod10=0
计算查找成功的平均查找长度:
a. 对于 SUN,查找时所需的比较次数为 1
b. 对于 MON,查找时所需的比较次数为 1
c. 对于 TUE,查找时所需的比较次数为 2
d. 对于 WED,查找时所需的比较次数为 1
e. 对于 THU,查找时所需的比较次数为 3
f. 对于 FRI,查找时所需的比较次数为 4
g. 对于 SAT,查找时所需的比较次数为 6
根据等概率下查找成功的平均查找长度计算公式可得
计算查找不成功的平均查找长度:
a. 对于哈希函数取值为 0 时,查找不成功时的比较次数为 2
b. 对于哈希函数取值为 1 时,查找不成功时的比较次数为 1
c. 对于哈希函数取值为 2 时,查找不成功时的比较次数为 2
d. 对于哈希函数取值为 3 时,查找不成功时的比较次数为 1
e. 对于哈希函数取值为 4 时,查找不成功时的比较次数为 1
f. 对于哈希函数取值为 5 时,查找不成功时的比较次数为 7
g. 对于哈希函数取值为 6 时,查找不成功时的比较次数为 6
根据等概率下查找不成功的平均查找长度计算公式可得
【例 3】已知某哈希表的装载因子小于 1,哈希函数 H(key)为关键字(标识符)的第一个字母在字母表中的序号,处理冲突的方法为线性探测开放定址法。
1 编写按第一个字母的顺序输出哈希表中所有关键字的算法。
2 编写模拟手工计算等概率情况下查找不成功的平均查找长度算法。
解:此题重在训练算法实现。
解①:
【问题分析】
因哈希表的装载因子小于 1,哈希表未满。要实现按第一个字母的顺序输出哈希表中所有关键字的要求,可按字母序号从 1 到 26 的顺序逐个找出对应每个序号的所有关键字的方式加以实现。
实现步骤:对字母序号为 i,可先从该字母序号位置开始判断该处所存关键字的哈希函数值是否为 i,若相同,则表示该关键字为与序号 i 对应的关键字,输出该值,若不同,表示该关键字并非为字母序号 i 对应的关键字,不输出。按线性探测开放定址法继续判断下一个关键字,直到值为空。字母序号从 1 到 26 持续上述步骤,则可得一个按字母顺序输出的所有关键字序列。
【算法描述】
void printword(HashTable ht)
/*按第一个字母的顺序输出哈希表ht中的关键字,处理冲突的方法为线性探测开放定址法*/
{
int i,j;
for(i=1; i<=26; i++) /*按字母顺序输出关键字*/
{
j = i; /*哈希地址j初始为序号I*/
while(ht[j].key != NULLKEY) /*判断关键字是否为空*/
{
if(hash(ht[j].key) == i) /*若关键字与序号i对应,则输出该关键字*/
printf("%s\n",ht[j].key);
j = (j+1)%m; /*按线性探测规则更新地址j*/
}
}
}
解②:
【问题分析】
根据手工计算等概率情况下查找不成功的平均查找长度公式
知模拟手工计算查找不成功的平均查找长度可分为 3 步实现:先针对每个哈希函数值,确定查找不成功的比较次数,设一个计数器 count 记录比较次数;其次把对应每个哈希函数值的比较次数相加得一总的比较次数;最后将总比较次数除以哈希函数取值个数就可得查找不成功的平均查找长度。
【算法描述】
float unsucclength(HashTable ht)
/*模拟手工计算在等概率情况下查找哈希表 ht 不成功的平均查找长度算法 */
{
int i,j;
int count;
int asl = 0;
float unasl;
for(i=1; i<=26; i++) /*对每个哈希函数值,计算查找不成功的比较次数*/
{
j = i;
count = 1; /*计数器count暂存每个哈希函数值的确定查找不成功的比较次数*/
while(ht[j].key != NULLKEY)
{
count++; /*计算对应每个哈希函数值的不成功查找长度*/
j=(j+1)%m;
}
asl = asl + count; /*计算不成功查找长度总和*/
}
unasl = (float)asl/26; /*求得不成功的平均查找长度*/
return (unasl);
}