数据结构(C语言版)严蔚敏 吴伟民 编著 第9章 查找

前言

本书在第2章和第7章已经介绍了各种线性和非线性的数据结构,在这一章将讨论另一种在实际应用中大量使用的数据结构——查找表。
查找表是由同一类型的数据元素(或记录)构成的集合。由于集合中的数据元素之间存在着完全松散的关系,因此查找表是一个非常灵便的数据结构。
对查找表经常进行的操作有:
(1)查询某个特定的数据元素是否在查找表中;
(2)检索某个特定的数据元素的各种属性;
(3)在查找表中插入一个数据元素;
(4)从查找表中删去某个数据元素
若对查找表只作前两种统称为“查找”的操作,则称此类查找表尾静态查找表。若在查找过程中同时插入查找表中不存在的数据元素,或者从查找表中删除已存在的某个数据元素,则称此类表为动态查找表。
关键字是数据元素(或记录)中某个数据项的值,用它可以标识(识别)一个数据元素(或记录)。若次关键字可以唯一地标识一个记录,则称此关键字为主关键字。反之,称用以识别若干记录的关键字为次关键字。当数据元素只有一个数据项时,其关键字即为该数据元素的值。
查找:根据给定的某个值,在查找表中确定一个其关键字等于给定值的记录或数据元素。
如何进行查找?显然,在一个结构中查找某个数据元素的过程依赖于这个数据元素在结构中所处的地位。因此,对表进行查找的方法取决于表中数据元素依何种关系(这个关系是人为地加上去的)组织在一起的。同样在计算机中进行查找的方法也随数据结构不同而不同。本章讨论的查找表是一种非常灵便的数据结构。但也正是由于表中数据元素之间仅存在着“同属一个集合”的松散关系,给查找带来不便。为此,需在数据元素之间人为地加上一些关系,以便按某种规则进行查找,即以另一种数据结构来表示查找表。

// 在本章以后各节的讨论中,涉及的关键字类型和数据元素类型统一说明如下:
// 典型的关键字类型说明可以是:
typedef float KeyType;      // 实型
typedef int   KeyType;      // 整型
typedef char *KeyType;      // 字符串型

数据元素类型定义为:
typedef struct{
	KeyType key;      // 关键字域
	...               // 其他域
}SElemType;

// 对两个关键字的比较约定为如下的宏定义
// --对数值型关键字
	#define EQ(a,b)  ((a) ==(b))
	#define LT(a,b)  ((a) < (b))
	#define LQ(a,b)  ((a)<= (b))
	...
	// --对字符串型关键字
	#define EQ(a,b)  (!strcmp((a),(b)))
	#define LT(a,b)  (strcmp((a),(b)) < 0)
	#define LQ(a,b)  (strcmp((a),(b)) <= 0)
	...

9.1 静态查找表

9.1.1 顺序表的查找

以顺序表或线性链表表示静态查找表,则Search函数可用顺序查找来实现。本节中只讨论它在顺序存储结构模块中的实现。

// 静态查找表的顺序存储结构
typedef struct{
	ElemType  *elem;   // 数据元素存储空间基址,建表时按实际长度分配,0号单元留空
	int length;        // 表长度
}SSTable;

顺序查找的查找过程为:从表中最后一个记录开始,逐个进行记录的关键字和给定值的比较,若某个记录的关键字和给定值比较相等,则查找成功,找到所查记录;反之,若直至第一个记录,其关键字和给定值比较都不等,则表明表中没有所查记录,查找不成功。

int Search_Seq(SSTable ST, KeyType key){
	// 在顺序表ST中顺序查找其关键字等于Key的数据元素,若找到则函数值为该元素在表中的位置,否则为0
	ST.elem[0].key = key;  // “哨兵”
	for(i = ST.length; !EQ(ST.elem[i].key, key); --i)   // 从后往前找
	return i;                                           // 找不到时,i为0
}

平均查找长度(Average Search Length):为确定记录在查找表中的位置,需和给定值进行比较的关键字个数的期望值称为查找算法在查找成功时的平均查找长度。
对于含n个记录的表,查找成功时的平均查找长度为:ASL=(n+1)/2
顺序查找的缺点是平均查找长度较大,特别是当n很大时,查找效率较低。然而,它有很大的优点是算法简单且适用性广。
当查找不成功的情况不能忽视时,查找算法的平均查找长度应是查找成功时的平均查找长度与查找不成功时的平均查找长度之和。此时顺序查找的平均查找长度为:
ASL = 3(n+1)/4

9.1.2 有序表的查找

以有序表表示静态查找表时,Search函数可用折半查找来实现。
折半查找的查找过程是:先确定待查记录所在的范围(区间),然后逐步缩小范围直到找到或找不到该记录为止。折半查找过程是以处于区间中间位置记录的关键字和给定值比较,若相等则查找成功,若不等则缩小范围,直至新的区间中间位置记录的关键字等于给定的值或者区间的大小小于零时(表明查找不成功)为止。

int Search_Bin(SSTable ST, KeyType key){
	// 在有序表ST中折半查找其关键字等于key的数据元素,若找到则函数值为该元素在表中的位置,否则为0
	low = 1; high = ST.length;        // 置区间初值
	while(low < high){
		mid = (low + high )/2;
		if(EQ(key, ST.elem[mid].key))  return mid;    // 找到待查元素
		else if(LT(key, ST.elem[mid].key))  high = mid -1;  // 继续在前半区间进行查找
		else low = mid +1;                                  // 继续在后半区间进行查找
	}
	return 0;                                               // 顺序表中不存在待查元素
}// Search_Bin                         

该查找过程所用来描述查找过程的二叉树为判定树。一个具有n个结点的判定树的深度为⌊ log2n⌋+1,所以折半查找法在查找成功时和给定值进行比较的关键字个数至多为⌊ log2n⌋+1。折半查找在查找不成功时和给定值进行比较的关键字个数最多也不超过⌊ log2n⌋+1。
折半查找的平均查找长度ASL = (n+1)/n log2(n+1)-1
对任意的n,当n较大(n>50)时,可有下列近似结果:
ASLbs=log2(n+1)-1
可见,折半查找的效率比顺序查找高,但折半查找只适用于有序表,且限于顺序存储结构,对线性链表无法有效地进行折半查找。以有序表表示静态查找表时,进行查找的方法除折半查找之外,还有菲波那切查找和插值查找。菲波那切查找是根据菲波那切序列的特点对表进行分割的。平均性能比折半查找好,但最坏情况下的性能却比折半查找差。它还有一个优点就是分割时只需进行加、减运算。插值查找只适用于关键字均匀分布的表。

9.1.3 静态树表的查找

当有序表中各记录的查找概率不等时,按照之前判定树进行折半查找,其性能未必是最优的,那么此时应如何进行查找呢?换句话说,描述查找过程的判定树为何类二叉树时,其查找性能最佳?如果只考虑查找成功的情况,则使查找性能能达最佳的判定树是其带权内路径长度之和PH值
PH = ∑ i = 1 n w i h i \sum_{i=1}^{n} w_ih_i i=1nwihi
取最小值的二叉树。其中n为二叉树上的结点的个数,即有序表的长度,hi为第i个结点在二叉树上的层次数,结点的权wi = cpi,其中pi为结点的查找概率,c为某个常量。称PH值取最小的二叉树为静态最优查找树。构造静态最优查找树花费的时间代价较高,因此通常采用的是一种构造近似最优查找树的有效算法:
已知一个按关键字有序的记录序列:
(rl,rl+1,…rh
其中rl.key < rl+1.key< … <rh.key
与每个记录相应的权值为:
wl,wl+1,…wh
现构造一棵二叉树,使这棵二叉树的带权内路径长度PH值在所有具有同样权值的二叉树中近似为最小,称这类二叉树为次优查找树。

9.1.4 索引顺序表的查找

若以索引顺序表表示静态查找表,则Search函数可用分块查找来实现。分块查找又称索引顺序查找,这是顺序查找的一种改进方式。在此查找法中,除表本身以外,尚需建立一个索引表。对每个子表建立一个索引项,其中包括两项内容:关键字项,其值为该子表内的最大关键字,指针项,指示该子表的第一个记录在表中位置。索引表按关键字有序,则表或者有序或者分块有序。所谓分块有序指的是第二个子表中所有记录的关键字均大于第一个子表中的最大关键字,第三个子表中的所有关键字均大于第二个子表中的最大关键字,以此类推。
因此,分块查找过程需分两步进行。先确定待查记录所在的块,然后在块中查找。由于在索引项组成的索引表按关键字有序,则确定块的查找可以用顺序查找,亦可用折半查找,而块中记录是任意的,则在块中只能是顺序查找。
分块查找的平均查找长度为:
ASLbs=Lb+Lw
其中Lb为查找索引表确定所在块的平均查找长度,Lw为在块中查找元素的平均查找长度。一般情况下,为进行分块查找,可以将长度为n的表均匀地分为b块,每块含有s个记录,即b=⌈ n/s⌉,又假定表中每个记录的查找概率相等,则每块查找的概率为1/b,块中每个记录的查找概率为1/s。
若用顺序查找确定块,则分块查找的平均查找长度为:
ASLbs=Lb+Lw=½(n/s + s)+1
若用折半查找确定所在块,则分块查找的平均查找长度为:
ASLbs ≈log2(n/s + 1) + s/2

9.2 动态查找表

动态查找表的特点是,表结构本身是在查找过程中动态生成的,即对于给定值key,若表中存在其关键字等于key的记录,则查找成功返回,否则插入关键字等于key的记录。

9.2.1 二叉排序树和平衡二叉树

  1. 二叉排序树及其查找过程
    二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:
    (1)若它的左子树不空,则左子树上所有结点的值均小于它的根节点的值
    (2)若它的右子树不空,则右子树上所有结点的值均大于它的根节点的值
    (3)它的左、右子树也分别为二叉排序树
    二叉排序树又称为二叉查找树,它的查找过程和次优二叉树类似。即当二叉排序树不空时,首先将给定值和根节点的关键字比较,若相等,则查找成功,否则将依据给定值和根节点的关键字之间的大小关系,分别在左子树或右子树上继续进行查找。通常,可取二叉链表作为二叉排序树的存储结构,则该算法如下:
BiTree SearchBST(BiTree T, KeyType key){
	// 在根指针T所指二叉排序树中递归地查找某关键字等于key的数据元素,若查找成功,则返回指向该元素结点的指针,否则返回空指针
	if(!T)|| EQ(key, T-> data.key) return T;      // 查找结束
	else if LT(key, T->data.key) return (SearchBST(T-> lchild, key));  // 在左子树中继续查找
	else return(SearchBST(T-> rchild, key));                           // 在右子树中继续查找
}// SearchBST
  1. 二叉排序树的插入和删除
    和次优二叉树相对,二叉排序树是一个动态链表。其特点是,树的结构通常不是一次生成的,而是在查找过程中,当树中不存在关键字等于给定值的结点时再进行插入。新插入的结点一定是一个新添加的叶子结点看,并且是查找不成功时查找路径上访问的最后一个结点的左孩子或右孩子结点。为此,需将上一节的二叉排序树的查找算法改成下述算法,以便能在查找不成功时返回插入位置:
Status SearchBST(BiTree T, KeyType key, BiTree f, BiTree &p){
	// 在根指针T所指二叉排序树中递归地查找其关键字等于key的数据元素
	// 若查找成功,则指针p指向该数据元素结点,并返回TRUE
	// 否则指针p指向查找路径上访问的最后一个结点并返回FALSE,指针f指向T的双亲,其初始调用值为NULL
	if(!T)  {p = f; return FALSE;}      // 查找不成功
	else if EQ(key,T->data.key) {p = T; return TRUE;}   // 查找成功
	else if LT(key,T->data.key)  return SearchBST(T->lchild, key, T,p); // 在左子树中继续查找
	else return SearchBST(T->rchild,key,T,p);                           // 在右子树中继续查找
}// SearchBST

插入算法如下所示:

Status insertBST(BiTree &T, ElemType e){
	// 当二叉排序树中不存在关键字等于e.key的数据元素时,插入e并返回TRUE,否则返回FALSE
	if(!SearchBST(T,e.key,NULL,p)){    // 查找不成功
		s = (BiTree)malloc(sizeof(BiTNode));
		s->data = e; s->lchild = s->rchild = NULL;
		if(!p) T=s;                    // 被插结点*s为新的根节点
		else if LT(e.key, p->data.key) p ->lchild = s;  // 被插结点*s为左孩子
		else p->rchild = s;                             // 被插结点*s为右孩子
		return TRUE;
	}
	else return FALSE;
}// Insert BST

一个无序序列可以通过构造一棵二叉排序树而变成一个有序序列,构造树的过程即为对无序序列进行排序的过程。不仅如此,每此插入的新结点都是二叉排序树上新的叶子结点,即在进行插入操作时,不必移动其他结点,仅需改动某个结点的指针,由空变为非空即可。这就相当于在一个有序序列上插入一个记录而不需要移动其他记录。它表明,二叉排序树既拥有类似于折半查找的特性,又采用了链表作存储结构,因此是动态查找表的一种适宜表示。同样在二叉排序树上删去一个结点也很方便。对于一般的二叉树来说,删去树中的一个结点时没有意义的。因为它将使以被删结点为根的子树成为森林,破坏了整棵树的结构。然而,对于二叉排序树,删去树上一个结点相当于删去有序序列中的一个记录,只要在删除某个结点之后依旧保持二叉排序树的特性即可。
在二叉排序树上删除一个结点的算法如下所示:

Status DeleteBST(BiTree &T,KeyType key){
	// 若二叉排序树T中存在关键字等于key的数据元素时,则删除该数据元素结点,并返回TRUE,否则返回FALSE
	if(!T) return FALSE;    // 不存在关键字等于key的数据元素
	else{
		if(EQ(key, T-> data.key)) return Delete(T);   // 找到关键字等于key的数据元素
		else if (LT(key, T->data.key)) return DeleteBST(T->lchild,key);
		else return DeleteBST(T->rchild,key);
	}
}// DeleteBST

其中删除操作如下所示:

Status Delete(BiTree &p){
	// 从二叉排序树中删除结点p,并重接它的左或右子树
	if(!p->rchild){    // 右子树空则只需重接它的左子树
		q = p; p = p->lchild; free(q);	
	}
	else if(!p->lchild){    // 只需重接它的右子树
		q = p; p =p->rchild; free(q);
	}
	else {                  // 左右子树均不空
		q = p; s = p->lchild;
		while(s->rchild) {q=s; s= s->rchild} // 转左,然后向右到尽头
		p->data = s->data;                   // s指向被删结点的“前驱”
		if(q!=p) q->rchild = s->lchild;      // 重接*q的右子树
		else q->lchild = s->lchild;          // 重接*q的左子树
		delete s;
	}
	return TRUE;
}// Delete
  1. 二叉排序树的查找分析
    含有n个结点的二叉排序树的平均查找长度和树的形态有关。当先后插入的关键字有序时,构成的二叉排序树蜕变成单支树。树的深度为n,其平均查找长度为(n+1)/2,这是最差的情况。显然,最好的情况是二叉排序树的形态和折半查找的判定树相同,其平均查找长度和log2n成正比。由此可见,在随机的情况下,二叉排序树的平均查找长度和log n是等数量级的。
  2. 平衡二叉树
    平衡二叉树(Balanced Binary Tree 或 Height-Balanced Tree)又称AVL树。它或者是一棵空树,或者是具有下列性质的二叉树:它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值不超过1。若将二叉树上的结点的平衡因子BF定义为该结点的左子树的深度减去它的右子树的深度,则平衡二叉树上所有结点的平衡因子只可能是-1、0和1。
    因为AVL树上任何结点的左右子树的深度之差都不超过1,则可以证明它的深度和log n是同数量级的。由此,它的平均查找长度也和log n同数量级的。
  3. 平衡树查找的分析
    在平衡树上进行查找的时间复杂度为O(log n)。上述对二叉排序树和二叉平衡树的查找性能的讨论都是在等概率的前提下进行的,若查找概率不等,为了提高查找效率,需要对待查记录序列先进行排序,使其按关键字递增(或递减)有序,然后构造一棵次优查找树。显然,次优查找树也是一棵二叉排序树,但次优查找树不能在查找过程中插入结点生成。二叉排序树(或称二叉查找树)是动态树表,最优或次优查找树是静态树表。

9.2.2 B-树和B+树

  1. B-树及其查找
    B-树是一种平衡的多路查找树,它在文件系统中很有用。先介绍这种树的结构及其查找算法。
    一棵m阶的B-树,或为满足下列特性的m叉树:
    (1)树中每个结点至多有m棵子树
    (2)若根结点不是叶子结点,则至少有2棵子树
    (3)除根之外的所有非终端结点至少有 ⌈ m/2 ⌉棵子树
    (4)所有的非终端结点中包含下列信息数据
    (n,A0,K1,A,K2,A2,…Kn,An
    其中,Ki(i = 1,…,n)为关键字,且Ki<Ki+1(i=1,…n-1),Ai(i=0,…n)为指向子树根节点的指针,且指针Ai-1所指子树中所有结点的关键字均小于Ki(i=1,…,n),An所指子树中所有结点的关键字均大于Kn,n(⌈ m/2 ⌉ - 1≤n≤m-1)为关键字的个数(或n+1为子树个数)。
    (5)所有的叶子结点都出现在同一层次上,并且不带信息,可以看作是外部结点或查找失败的结点,实际上这些结点不存在,指向这些结点的指针为空。
    在B-树上进行查找的过程是一个顺指针查找结点和在结点的关键字中进行查找交叉进行的过程。由于B-树主要用作文件的索引,因此它的查找涉及外存的存取,在此略去外存的读写,只作示意性的描述,假设结点类型如下说明:
#define m 3                // B-树的阶,暂设为3
typedef struct BTNode{
	int               keynum;          // 结点中关键字个数,即结点的大小
	struct BTNode    *parent;          // 指向双亲结点
	KeyType         key[m+1];          // 关键字向量,0号单元未用
	struct BTNode  *ptr[m+1];          // 子树指针向量
	Record      *recptr[m+1];          // 记录指针向量,0号单元未用
}BTNode, *BTree;
typedef struct{
	BTNode     *pt;                    // 指向找到的结点
	int          i;                    // 1...m,在结点中的关键字序号
	int        tag;                    // 1:查找不成功,0:查找失败
}Result;

下述算法简要地说明了B-树的查找操作的实现:

Result SearchBTree(BTree T, KeyType K){
	// 在m阶B-树T上查找关键字K,返回结果(pt,i,tag)。若查找成功,则特征值tag =1,指针pt所指结点中第i个关键字等于K
	// 否则特征值tag = 0,等于K的关键字应插入在指针pt所指结点中第i和第i+1个关键字之间
	p =T; q = NULL; found = FALSE; i = 0;   // 初始化,p指向待查结点,q指向p的双亲
	while(p && ! found){
		i = Search(p,K);      // 在p->key[1..keynum]中查找i使得:p->key[i]≤K<p->key[i+1]
		if(i>0 && p->key[i]==K) found = TRUE;   // 找到待查关键字
		else{q = p; p = p->ptr[i];}
	}
	if(found)   return (p,i,1);    // 查找成功
	else return (q,i,0);           // 查找不成功,返回K的插入位置信息
}// SearchBTree
  1. B-树查找分析
    在B-树上进行查找包含两种基本操作:
    (1)在B-树中找结点
    (2)在结点中找关键字
    由于B-树通常存储在磁盘上,则前一查找操作是在磁盘上进行的,而后一查找操作是在内存中进行的,即在磁盘上找到指针p所指结点后,先将结点中的信息读入内存,然后再利用顺序查找或折半查找查询等于K的关键字。显然,在磁盘上进行一次查找比在内存中进行一次查找耗费时间多得多,因此,在磁盘上进行查找的次数,即待查关键字所在结点在B-树上的层次数,是决定B-树查找效率的首要因素。
    在含有N个关键字的B-树上进行查找时,从根节点到关键字所在结点的路径上涉及的结点数不超过log⌈m/2⌉((N+1)/2) +1。
  2. B-树的插入和删除
    B-树的生成也是从空树起,逐个插入关键字而得。但由于B-树结点中的关键字个数必须≥⌈m/2⌉-1,因此,每次插入一个关键字不是在树中添加一个叶子结点,而是首先在最低层的某个非终端结点中添加一个关键字,若该结点的关键字个数不超过m-1,则插入完成,否则要产生结点的“分裂”。
    反之,如果在B-树上删除一个关键字,则首先应找到该关键字所在结点,并从中删除之,若该结点为最下层的非终端结点,且其中的关键字数目不少于⌈m/2⌉,则删除之,否则要进行“合并”结点的操作。假若所删关键字为非终端结点中的Ki,则可以指针Ai所指子树中的最小关键字Y替代Ki,然后在相应的结点中删去Y。
  3. B+
    B+树是应文件系统所需而出的一种B-树的变型树。一棵m阶的B+树和B-树的差异在于:
    (1)有n棵子树的结点中含有n个关键字
    (2)所有的叶子结点中包含了全部关键字的信息,及指向含这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接
    (3)所有的非终端结点可以看成是索引部分,结点中仅含有其子树(根节点)中的最大(或最小)关键字。

9.3 哈希表

9.3.1 什么是哈希表

在前面讨论的各种结构(线性表、树等)中,记录在结构中的相对位置是随机的,和记录的关键字之间不存在确定的关系,因此在结构中查找记录时需进行一系列和关键字的比较。这一类查找方法建立比较的基础上,查找的效率依赖于查找过程中所进行的比较次数。
理想的情况是希望不经过任何比较,依次存取变能得到所查记录,那就必须在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使每个关键字和结构中一个唯一的存储位置相对应。因而在查找时,只要根据这个关系f找到给定值K的像f(K)。若结构中存在关键字和K相等的记录,则必定在f(K)的存储位置上,由此,不需要进行比较便可取得所查记录。在此,我们称这个对应关系f为哈希(Hash)函数,按这个思想建立的表为哈希表。
哈希表:根据设定的哈希函数H(key)和处理冲突的方法将一组关键字映射到一个有限的连续的地址集(区间)上,并以关键字在地址集中的“像”作为记录在表中的存储位置,这种表便称为哈希表,这一映像过程称为哈希造表或散列,所得存储位置称为哈希地址或散列地址。

9.3.2 哈希表的构造方法

若对于关键字集合中的任一个关键字,经哈希函数映像到地址集合中任何一个地址的概率是相等的,则称此类哈希函数为均匀哈希函数。换句话说,就是使关键字经过哈希函数得到一个“随机的地址”,以便使一组关键字的哈希地址均匀分布在整个地址空间中,从而减少冲突。
常用的构造哈希函数的方法有:

  1. 直接定址法
    取关键字或关键字的某个线性函数值为哈希地址。
  2. 数字分析法
    假设关键字是以r为基的数,并且哈希表中可能出现的关键字都是事先知道的,则可取关键字的若干数位组成哈希地址。
  3. 平方取中法
    取关键字平方后的中间几位为哈希地址。
  4. 折叠法
    将关键字分割成维数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为哈希地址,这方法称为折叠法。关键字位数很多,而且关键字中每一位上数字分布大致均匀时,可以采用折叠法得到哈希地址。
  5. 除留余数法
    取关键字被某个不大于哈希表表长m的数p除后所得余数为哈希地址。
  6. 随机数法
    选择一个随机函数,取关键字的随机函数值为它的哈希函数。通常,当关键字长度不等时采用此法构造哈希函数较恰当。
    实际工作中需视不同的情况采用不同的哈希函数,通常考虑的因素有:
    (1)计算哈希函数所需时间,包括硬件指令的因素
    (2)关键字的长度
    (3)哈希表的大小
    (4)关键字的分布情况
    (5)记录的查找频率

9.3.3 处理冲突的方法

  1. 开放定址法
    Hi = (H(key)+ di)MOD m i = 1,2,…,k(k≤m-1)
    其中,H(key)为哈希函数,m为哈希表表长,di为增量序列,可分为线性探测再散列,二次探测再散列,伪随机数序列。
  2. 再哈希法
    Hi = RHi(key) i =1,2,… ,k
  3. 链地址法
    将所有关键字为同一词的记录存储在同一个线性链表中。
  4. 建立一个公共溢出区
    所有关键字和基本表中关键字为同义词的记录,不管它们由哈希函数得到的哈希地址是什么,一旦发生冲突,都填入溢出表。
    从哈希表的查找过程可见:
    (1)虽然哈希表在关键字与记录的存储位置之间建立了直接映像,但由于“冲突”的产生,使得哈希表的查找过程仍然是一个给定值和关键字进行比较的过程。因此,仍需以平均查找长度作为衡量哈希表的查找效率的量度。
    (2)查找过程中徐鹤给定值进行比较的关键字的个数取决于下列三个因素:哈希函数、处理冲突的方法和哈希表的装填因子。
1.对于二叉排序树,下面的说法( )是正确的。 A.二叉排序树是动态树表,查找不成功时插入新结点时,会引起树的重新分裂和组合 B.对二叉排序树进行层序遍历可得到有序序列 C.用逐点插入法构造二叉排序树时,若先后插入的关键字有序,二叉排序树的深度最大 D.在二叉排序树中进行查找,关键字的比较次数不超过结点数的1/2 2.在有n个结点且为完全二叉树的二叉排序树中查找一个键值,其平均比较次数的数量级为( )。 A.O(n) B.O(log2n) C.O(n*log2n) D.O(n2) 3.静态查找与动态查找的根本区别在于( )。 A. 它们的逻辑结构不一样 B. 施加在其上的操作不同 C. 所包含的数据元素类型不一样 D. 存储实现不一样 4.已知一个有序表为{12,18,24,35,47,50,62,83,90,115,134},当折半查找值为90的元素时,经过( )次比较查找成功。 A.2 B.3 C.4 D.5 5.已知数据序列为(34,76,45,18,26,54,92,65),按照依次插入结点的方法生成一棵二叉排序树,则该树的深度为( )。 A. 4 B. 5 C. 6 D. 7 6.设散列表表长m=14,散列函数H(k)=k mod 11 。表中已有15,38,61,84四个元素,如果用线性探测法处理冲突,则元素49的存储地址是( )。 A. 8 B. 3 C. 5 D. 9 7. 平衡二叉树的查找效率呈( )数量级。 A. 常数阶 B. 线性阶 C. 对数阶 D. 平方阶 8. 设输入序列为{20,11,12,…},构造一棵平衡二叉树,当插入值为12的结点时发生了不平衡,则应该进行的平衡旋转是( )。 A. LL B. LR C. RL D. RR 二、填空题(每空3分,共24分)。 1.在有序表A[1..18]中,采用二分查找算法查找元素值等于A[7]的元素,所比较过的元素的下标依次为 。 2.利用逐点插入法建立序列(61,75,44,99,77,30,36,45)对应的二叉排序树以后,查找元素36要进行 次元素间的比较查找序列为 。 3. 用顺序查找法在长度为n的线性表中进行查找,在等概率情况下,查找成功的平均比较次数是 。 4. 二分查找算法描述如下: intSearch_Bin(SST ST, KT key) { low=1 ; high=ST. length; while(low<=high) { mid=(low+high)/2; if(key==ST.elem[mid].key) return mid; else if(key<ST.elem[mid].key) ; else ; } return 0; } 5.链式二叉树的定义如下: typedef struct Btn{ TElemType data; ; }BTN ,*BT; 6.在有n个叶子结点的哈夫曼树中,总结点数是 。 三、综合题(共52分)。 1. (共12分)假定关键字输入序列为19,21,47,32,8,23,41,45,40,画出建立二叉平衡树的过程。 2. (共15分)有关键字{13,28,31,15,49,36,22,50,35,18,48,20},Hash 函数为H=key mod 13,冲突解决策略为链地址法,请构造Hash表(12分),并计算平均查找长度(3分)。 ASL= 3. (共10分)设关键字码序列{20,35,40,15,30,25},给出平衡二叉树的构造过程。 4. (共15分)设哈希表长为m=13,散列函数为H(k)=k mod 11,关键字序列为5,7,16,12,11,21,31,51,17,81;试求:散列后的表中关键字分布(假定解决冲突的方法为线性探测再散列法);求平均查找长度ASL;计算该表的装填因子。 (1)按要求求哈希表(10分): 0 1 2 3 4 5 6 7 8 9 10 11 12 (2)计算ASL(3分): ASL= (3)计算装填因子(2分):装填因子=
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值