文章目录
树形查找
考纲内容
(一)查找的基本概念
(二)顺序查找法
(三)分块查找法
(四)折半查找法
(五)树形查找
二叉搜索树;平衡二叉树;红黑树
(六)B树及其基本操作、B+树的基本概念
(七)散列(Hash)表
(八)查找算法的分析及应用
知识框架
复习提示
- 本章是考研命题的重点。
- 对于折半查找,应掌握折半查找的过程、构造判定树、分析平均查找长度等。
- 对于二叉排序树、二叉平衡树和红黑树,要了解它们的概念、性质和相关操作等。
- B 树和 B+树是本章的难点。对于B树,考研大纲要求掌握插入、删除和査找的操作过程;
- 对于 B+树,仅要求了解其基本概念和性质。
- 对于散列查找,应掌握散列表的构造、冲突处理方法(各种方法的处理过程)、查找成功和查找失败的平均查找长度、散列查找的特征和性能分析。
1.二叉排序树(BST)
构造一棵二叉排序树的目的并不是排序,而是提高查找、插入和删除关键字的速度,二叉排序树这种非线性结构也有利于插入和删除的实现。
1.1二叉排序树的定义
【命题追踪——二叉排序树的应用】
二叉排序树(也称二叉查找树)或者是一棵空树,或者是具有下列特性的二叉树:
1) 若左子树非空,则左子树上所有结点的值均小于根结点的值。
2) 若右子树非空,则右子树上所有结点的值均大于根结点的值。
3) 左、右子树也分别是一棵二叉排序树。
【命题追踪——二叉排序树中结点值之间的关系】
根据二叉排序树的定义,左子树结点值 < 根结点值 < 右子树结点值,因此对二叉排序树进行中序遍历,可以得到一个递增的有序序列。
例如,图7.4所示二叉排序树的中序遍历序列为 123468。
1.2二叉排序树的查找
二叉排序树的查找是从根结点开始,沿某个分支逐层向下比较的过程。
- 若二叉排序树非空,先将给定值与根结点的关键字比较,若相等,则查找成功;
- 若不等,若小于根结点的关键字,则在根结点的左子树上查找,否则在根结点的右子树上查找。
这显然是一个递归的过程。
二叉排序树的非递归查找算法:
BSTNode *BST_Search(BiTree T,ElemType key){
while(T!=NULL&&key!=T->data){ //若树空或等于根结点值,则结束循环
if(key<T->data) T=T->lchild; //小于,则在左子树上查找
else T=T->rchild; //大于,则在右子树上查找
}
return T;
}
例如,在图7.4中查找值为4的结点。
- 首先4与根结点6比较。由于4小于6,所以在根结点6的左子树中继续查找。
- 由于4大于2,所以在结点2的右子树中查找,查找成功。
同样,二叉排序树的查找也可用递归算法实现,递归算法比较简单,但执行效率较低。
1.3二叉排序树的插入
二叉排序树作为一种动态树表,其特点是树的结构通常不是一次生成的,而是在查找过程中,当树中不存在关键字值等于给定值的结点时再进行插入的。
插入结点的过程如下:
- 若原二叉排序树为空,则直接插入;
- 否则,若关键字k 小于根结点值则插入到左子树,
- 若关键字k大于根结点值,则插入到右子树。
- 插入的结点一定是一个新添加的叶结点,且是查找失败时的查找路径上访问的最后一个结点的左孩子或右孩子。
如图7.5所示在一棵二叉排序树中依次插入结点28和结点58,虚线表示的边是其查找的路径。
二叉排序树插入操作的算法描述如下:
int BST_Insert(BiTree &T,KeyType k){
if(T==NULL){ //原树为空,新插入的记录为根结点
T=(BiTree)malloc(sizeof(BSTNode));
T->data=k;
T->lchild=T->rchild=NULL;
return 1; //返回 1,插入成功
}
else if(k==T->data) //树中存在相同关键字的结点,插入失败
return 0;
else if(k<T->data) //插入T的左子树
return BST_Insert(T->lchild,k);
else //插入里的右子树
return BST_Insert(T->rchild,k);
}
1.4二叉排序树的构造
【命题追踪——构造二叉排序树的过程】
从一棵空树出发,依次输入元素,将它们插入二叉排序树中的合适位置。
设查找的关键字序列为{45,24,53,45,12,24},则生成的二叉排序树如图 7.6 所示。
构造二叉排序树的算法描述如下:
void Creat_BST(BiTree &T,KeyType str[],int n){
T=NULL; //初始时T为空树
int i=0;
while(i<n){ //依次将每个关键字插入二叉排序树
BST_Insert(T,str[i]);
i++;
}
}
1.5二叉排序树的删除
在二叉排序树中删除一个结点时,不能把以该结点为根的子树上的结点都删除,必须先把被删除结点从存储二叉排序树的链表上摘下,将因删除结点而断开的二叉链表重新链接起来,同时确保二叉排序树的性质不会丢失。
删除操作的实现过程按3种情况来处理:
① 若被删除结点z是叶结点,则直接删除,不会破坏二叉排序树的性质。
② 若结点z只有一棵左子树或右子树,则让z的子树成为z父结点的子树,替代z的位置。
③ 若结点z有左、右两棵子树,则令z的直接后继(或直接前驱)替代z,然后从二叉排序树中删去这个直接后继(或直接前驱),这样就转换成了第一或第二种情况。
图 7.7显示了在3种情况下分别删除结点45,78,78 的过程。
1.6二叉排序树的查找效率分析
二叉排序树的查找效率,主要取决于树的高度。
若二叉排序树的左、右子树的高度之差的绝对值不超过1(平衡二叉树,下一节),它的平均査找长度为。
若二叉排序树是一个只有右(左)孩子的单支树(类似于有序的单链表),则其平均查找长度为O(n)。
在最坏情况下,即构造二叉排序树的输入序列是有序的,则会形成一个倾斜的单支树,
此时二叉排序树的性能显著变坏,树的高度也增加为元素个数 n,如图 7.8(b)所示。
在等概率情况下,图7.8(a)查找成功的平均查找长度为
ASLa=(1+2x2+3x4+4x3)/10=2.9
而图 7.8(b)查找成功的平均查找长度为
ASLь=(1+2+3+4+5+6+7+8+9+10)/10=5.5
从查找过程看,二叉排序树与二分查找相似。
就平均时间性能而言,二叉排序树上的查找和二分查找差不多。
但二分查找的判定树唯一,而二叉排序树的查找不唯一,相同的关键字其插入顺序不同可能生成不同的二叉排序树,如图7.8所示。
就维护表的有序性而言,二叉排序树无须移动结点,只需修改指针即可完成插入和删除操作,平均执行时间为。
二分查找的对象是有序顺序表,若有插入和删除结点的操作,所花的代价是 O(n)。
当有序表是静态查找表时,宜用顺序表作为其存储结构,而采用二分查找实现其找操作;
若有序表是动态查找表,则应选择二叉排序树作为其逻辑结构。
2.平衡二叉树
2.1平衡二叉树的定义
为了避免树的高度增长过快,降低二叉排序树的性能,规定在插入和删除结点时,要保证任意结点的左、右子树高度差的绝对值不超过1,将这样的二叉树称为平衡二叉树(Balanced Binary Tree),也称 AVL树。
定义结点左子树与右子树的高度差为该结点的平衡因子,则平衡二叉树结点的平衡因子的值只可能是-1、0或1。
【命题追踪——平衡二叉树的定义】
因此,平衡二叉树可定义为或是一棵空树,或是具有下列性质的二叉树:
- 它的左子树和右子树都是平衡二叉树,
- 且左子树和右子树的高度差的绝对值不超过 1。
图 7.9(a)所示是平衡二叉树,图 7.9(b)所示是不平衡的二叉树。
结点中的数字为该结点的平衡因子。
2.2平衡二叉树的插入
二叉排序树保证平衡的基本思想如下:
- 每当在二叉排序树中插入(或删除)一个结点时,首先检查其插入路径上的结点是否因为此次操作而导致了不平衡。
- 若导致了不平衡,则先找到插入路径上离插入结点最近的平衡因子的绝对值大于1的结点A,
- 再对以A为根的子树,在保持二叉排序树特性的前提下,调整各结点的位置关系,使之重新达到平衡。
【命题追踪——平衡二叉树中插入操作的特点】
注意:
每次调整的对象都是最小不平衡子树,即以插入路径上离插入结点最近的平衡因子的绝对值大于1的结点作为根的子树。
图 7.10中的虚线框内为最小不平衡子树。
【命题追踪——平衡二叉树的插入及调整操作的实例】
平衡二叉树的插入过程的前半部分与二叉排序树相同,但在新结点插入后,若造成查找路径上的某个结点不再平衡,则需要做出相应的调整。
可将调整的规律归纳为下列4种情况:
1) LL平衡旋转(右单旋转)
由于在结点A的左孩子(L)的左子树(L)上插入了新结点,A的平衡因子由1增至 2,导致以A为根的子树失去平衡,需要一次向右的旋转操作。
将A的左孩子B向右上旋转代替A成为根结点,将A向右下旋转成为B的右孩子,而B的原右子树则作为A的左子树。
如图7.11所示,结点旁的数值代表结点的平衡因子,而用方块表示相应结点的子树,下方数值代表该子树的高度。
2) RR 平衡旋转(左单旋转)
由于在结点A的右孩子(R)的右子树(R)上插入了新结点,A 的平衡因子由-1 减至-2,导致以A为根的子树失去平衡,需要一次向左的旋转操作。
将A的右孩子B向左上旋转代替A成为根结点,将A向左下旋转成为B的左孩子,而B的原左子树则作为A的右子树,如图7.12 所示。
3) LR 平衡旋转(先左后右双旋转)
由于在A的左孩子(L)的右子树(R)上插入新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡,需要进行两次旋转操作,先左旋转后右旋转。
先将A的左孩子B的右子树的根结点C向左上旋转提升到B的位置,然后把结点 C向右上旋转提升到A的位置,如图7.13所示。
4) RL平衡旋转(先右后左双旋转)
由于在A的右孩子(R)的左子树(L)上插入新结点,A 的平衡因子由-1 减至-2,导致以 A为根的子树失去平衡,需要进行两次旋转操作,先右旋转后左旋转。
先将A的右孩子B的左子树的根结点C向右上旋转提升到B的位置,然后把结点C向左上旋转提升到A的位置,如图7.14所示。
注意:LR和 RL 旋转时,新结点究竟是插入C的左子树还是插入C的右子树不影响旋转过程,而图 7.13 和图 7.14 中以插入C的左子树中为例。
【命题追踪——构造平衡二叉树的过程】
以关键字序列{15,3,7,10,9,8}构造一棵平衡二叉树的过程为例,
- 图 7.15(d)插入7后导致不平衡,最小不平衡子树的根为 15,插入位置为其左孩子的右子树,所以执行LR旋转,先左后右双旋转,调整后的结果如图 7.15(e)所示。
- 图 7.15(g)插入9后导致不平衡,最小不平衡子树的根为15,插入位置为其左孩子的左子树,所以执行 LL旋转,右单旋转,调整后的结果如图 7.15(h)所示。
- 图 7.15(i)插入8后导致不平衡,最小不平衡子树的根为 7,插入位置为其右孩子的左子树,所以执行 RL旋转,先右后左双旋转,调整后的结果如图7.15(j)所示。
2.3平衡二叉树的删除
与平衡二叉树的插入操作类似,以删除结点w为例来说明平衡二叉树删除操作的步骤:
1) 用二叉排序树的方法对结点w执行删除操作。
2) 若导致了不平衡,则从结点 w开始向上回溯,找到第一个不平衡的结点 z(即最小不平衡子树);
y为结点z的高度最高的孩子结点;x是结点y的高度最高的孩子结点。
3) 然后对以z为根的子树进行平衡调整,其中x、y和z可能的位置有4种情况:
- y是z的左孩子,x是y的左孩子(LL,右单旋转):
- y是z的左孩子,x是y的右孩子(LR,先左后右双旋转):
- y是z的右孩子,x是y的右孩子(RR,左单旋转);
- y是z的右孩子,x是y的左孩子(RL,先右后左双旋转)。
这四种情况与插入操作的调整方式一样。不同之处在于,插入操作仅需要对以z为根的子树进行平衡调整;
而删除操作就不一样,先对以z为根的子树进行平衡调整,若调整后子树的高度减1,则可能需要对z的祖先结点进行平衡调整,甚至回溯到根结点(导致树高减1)。
以删除图 7.16(a)的结点 32 为例,由于 32为叶结点,直接删除即可,向上回溯找到第一个不平衡结点 44(即 z),z的高度最高的孩子结点为 78(y),y的高度最高的孩子结点为50(x),满足 RL情况,先右后左双旋转,调整后的结果如图7.16(c)所示。
2.4平衡二叉树的查找
【命题追踪——指定条件下平衡二叉树的结点数的分析】
在平衡二叉树上进行查找的过程与二叉排序树的相同。
因此,在查找过程中,进行关键字的比较次数不超过树的深度。
假设以表示深度为h的平衡二叉树中含有的最少结点数。
显然,有n0=0,n1=1,n2=2,并且有,如图7.17所示,依次推出n3=4,n4=7,n5= 12.…。
含有n个结点的平衡二叉树的最大深度为,因此平均査找效率为。
注意:该结论可用于求解给定结点数的平衡二叉树的查找所需的最多比较次数(或树的最大高度 )。
如在含有 12 个结点的平衡二叉树中查找某个结点的最多比较次数?