1、2019年3月2日星期六,第1页,第九章 查 找,2019年3月2日星期六,第2页,【课前思考】,在数学中,集合是一种简单得无法用数学语言精确定义的基本概念,一般认为,集合是一些具有确定可辨认特性事物构成的整体,只能判别某个特定事物是否属于某个集合,如果是,则称为是该集合的一个“元素”。集合中的元素彼此相异,但相互之间不存在任何关系,通俗地说,集合是由“一堆”事物构成的一个整体。 本章讨论的查找表正是如同数学中讨论的集合,因此它的主要操作就是判定某个数据元素是否属于该查找表。,2019年3月2日星期六,第3页,【学习目标】,1理解“查找表“的结构特点以及各种表示方法的适用性; 2熟练掌握以顺序
2、表或有序表表示静态查找表时的查找方法; 3熟悉静态查找树的构造方法和查找算法,理解静态查找树和折半查找的关系; 4熟练掌握二叉查找树的构造和查找方法; 5理解二叉平衡树的构造过程 ; 6熟练掌握哈希表的构造方法,深刻理解哈希表与其它结构的表的实质性的差别 ; 7掌握描述查找过程的判定树的构造方法,以及按定义计算各种查找方法在等概率情况下查找成功时的平均查找长度。,2019年3月2日星期六,第4页,【重点和难点】,本章重点在于理解查找表的结构特点及其各种表示方法的特点和适用场合。,【知识点】,顺序表、有序表、索引顺序表、二叉查找树、二叉平衡树、哈希表,2019年3月2日星期六,第5页,【学习指南
3、】,本章讨论的查找表即为绪论中提到的“集合“结构,由于它是很多应用软件中的操作对象,因此本章讨论的内容亦为整个课程的重点之一。由于集合中的数据元素之间不存在任何关系,因此它的主要操作“查找“不便进行,为了提高对查找表进行查找的效率需要以另一种数据结构表示之。因此在学习本章的过程中应该掌握各种表示方法的特点以便在实用中能灵活选用。本章要求必须完成的算法设计题为: 9.25,9.26,9.28,9.29,9.31.9.38,9.39 和 9.45。,2019年3月2日星期六,第6页,何谓查找表 ?,查找表是由同一类型的数据元素(或记录)构成的集合。,由于“集合”中的数据元素之间存在着松散的关系,因
4、此查找表是一种应用灵便的结构。,2019年3月2日星期六,第7页,对查找表经常进行的操作:,1)查询某个“特定的”数据元素是否在查找表中; 2)检索某个“特定的”数据元素的各种属性; 3)在查找表中插入一个数据元素; 4)从查找表中删去某个数据元素。,2019年3月2日星期六,第8页,仅作查询和检索操作的查找表。,静态查找表,有时在查询之后,还需要将“查询”结果为“不在查找表中”的数据元素插入到查找表中;或者,从查找表中删除其“查询”结果为“在查找表中”的数据元素。,动态查找表,查找表可分为两类:,2019年3月2日星期六,第9页,是数据元素(或记录)中某个数据项的值,用以标识(识别)一个数据
5、元素(或记录)。,关键字,若此关键字可以识别唯一的一个记录,则称之谓“主关键字”。,若此关键字能识别若干记录,则称 之谓“次关键字”。,2019年3月2日星期六,第10页,根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素或(记录)。,查找,若查找表中存在这样一个记录,则称“查找成功”。查找结果给出整个记录的信息,或指示该记录在查找表中的位置;否则称“查找不成功”。查找结果给出 “空记录”或“空指针”。,2019年3月2日星期六,第11页,由于查找表中的数据元素之间不存在明显的组织规律,因此不便于查找。为了提高查找的效率, 需要在查找表中的元素之间人为地 附加某种确定的关系,换句
6、话说, 用另外一种结构来表示查找表。,如何进行查找?,查找的方法取决于查找表的结构。,2019年3月2日星期六,第12页,9.1 静态查找表,9.2 动态查找树表,9.3 哈希表,2019年3月2日星期六,第13页,9.1 静 态 查 找 表,2019年3月2日星期六,第14页,数据对象D:,数据关系R:,D是具有相同特性的数 据元素的集合。每个数 据元素含有类型相同的 关键字,可唯一标识数 据元素。,数据元素同属一个集合。,ADT StaticSearchTable ,2019年3月2日星期六,第15页,Create(,Destroy(,Search(ST, key);,Traverse(S
7、T, Visit();,基本操作 P:, ADT StaticSearchTable,2019年3月2日星期六,第16页,构造一个含n个数据元素 的静态查找表ST。,Create(,操作结果:,2019年3月2日星期六,第17页,销毁表ST。,Destroy(,初始条件:操作结果:,静态查找表ST存在;,2019年3月2日星期六,第18页,若 ST 中存在其关键字等于key 的数据元素,则函数值为该元素的值或在表中的位置,否则为“空”。,Search(ST, key);,初始条件:操作结果:,静态查找表ST存在,key 为和查找表中元素的关键字类型相同的给定值;,2019年3月2日星期六,第1
8、9页,按某种次序对ST的每个元素调用函数Visit()一次且仅一次,一旦Visit()失败,则操作失败。,Traverse(ST, Visit();,初始条件:操作结果:,静态查找表ST存在,Visit 是对元素操作的应用函数;,2019年3月2日星期六,第20页,typedef struct / 数据元素存储空间基址,建表时/ 按实际长度分配,0号单元留空int length; / 表的长度 SSTable;,假设静态查找表的顺序存储结构为,ElemType *elem;,2019年3月2日星期六,第21页,数据元素类型的定义为:,typedef struct keyType key; /
9、关键字域 / 其它属性域 ElemType ;, TElemType ;,2019年3月2日星期六,第22页,一、顺序查找表,二、有序查找表,三、静态查找树表(*),四、索引顺序表,2019年3月2日星期六,第23页,以顺序表或线性链表表示静态查找表,一、顺序查找表,2019年3月2日星期六,第24页,ST.elem,回顾顺序表的查找过程:,假设给定值 e=64, 要求 ST.elemk = e, 问: k = ?,k,k,2019年3月2日星期六,第25页,int location( SqList L, ElemType /location,2019年3月2日星期六,第26页,ST.elem
10、,i,ST.elem,i,60,i,key=64,key=60,i,64,2019年3月2日星期六,第27页,int Search_Seq(SSTable ST, KeyType key) / 在顺序表ST中顺序查找其关键字等于/ key的数据元素。若找到,则函数值为/ 该元素在表中的位置,否则为0。ST.elem0.key = key; / “哨兵”for (i=ST.length; ST.elemi.key!=key; -i); / 从后往前找return i; / 找不到时,i为0 / Search_Seq,2019年3月2日星期六,第28页,定义: 查找算法的平均查找长度(Averag
11、e Search Length) 为确定记录在查找表中的位置,需和给定值进行比较的关键字个数的期望值其中: n 为表长,Pi 为查找表中第i个记录的概率,且 , Ci为找到该记录时,曾和给定值比较过的关键字的个数。,分析顺序查找的时间性能,2019年3月2日星期六,第29页,在等概率查找的情况下,顺序表查找的平均查找长度为:,对顺序表而言,Ci = n-i+1,ASL = nP1 +(n-1)P2 + +2Pn-1+Pn,2019年3月2日星期六,第30页,若查找概率无法事先测定,则查找过程采取的改进办法是,在每次查找之后,将刚刚查找到的记录直接移至表尾的位置上。,在不等概率查找的情况下,AS
12、Lss 在PnPn-1P2P1 时取极小值,2019年3月2日星期六,第31页,上述顺序查找表的查找算法简单,但平均查找长度较大,特别不适用于表长较大的查找表。,二、有序查找表,若以有序表表示静态查找表,则查找过程可以基于“折半”进行。,2019年3月2日星期六,第32页,ST.elem,ST.length,例如: key=64 的查找过程如下:,low,high,mid,low,mid,high,mid,low 指示查找区间的下界 high 指示查找区间的上界 mid = (low+high)/2,2019年3月2日星期六,第33页,int Search_Bin ( SSTable ST,
13、KeyType key ) low = 1; high = ST.length; / 置区间初值while (low = high) mid = (low + high) / 2;if (EQ (key , ST.elemmid.key) )return mid; / 找到待查元素else if ( LT (key , ST.elemmid.key) )high = mid - 1; / 继续在前半区间进行查找else low = mid + 1; / 继续在后半区间进行查找return 0; / 顺序表中不存在待查元素 / Search_Bin,2019年3月2日星期六,第34页,先看一个具
14、体的情况,假设:n=11,分析折半查找的平均查找长度,6,3,9,1,4,2,5,7,8,10,11,判定树,1,2,2,3,3,3,3,4,4,4,4,2019年3月2日星期六,第35页,假设 n=2h-1 并且查找概率相等 则 在n50时,可得近似结果,一般情况下,表长为 n 的折半查找的判定树的深度和含有 n 个结点的完全二叉树的深度相同。,2019年3月2日星期六,第36页,索引顺序表的查找过程:,1)由索引确定记录所在区间;,2)在顺序表的某个区间内进行查找。,注意:索引可以根据查找表的特点来构造。,可见,索引顺序查找的过程也是一个 “缩小区间”的查找过程。,四、索引顺序表,2019
15、年3月2日星期六,第37页,索引顺序查找的平均查找长度 =查找“索引”的平均查找长度+ 查找“顺序表”的平均查找长度,2019年3月2日星期六,第38页,9.2 动 态 查 找 树 表,2019年3月2日星期六,第39页,ADT DynamicSearchTable ,抽象数据类型动态查找表的定义如下:,数据对象D:数据关系R:,数据元素同属一个集合。,D是具有相同特性的数据元素的集合。 每个数据元素含有类型相同的关键字, 可唯一标识数据元素。,2019年3月2日星期六,第40页,InitDSTable(&DT),基本操作P:,DestroyDSTable(&DT),SearchDSTable
16、(DT, key);,InsertDSTable(,DeleteDSTable(,TraverseDSTable(DT, Visit();,ADT DynamicSearchTable,2019年3月2日星期六,第41页,操作结果:,构造一个空的动态查找表DT。,InitDSTable(,2019年3月2日星期六,第42页,销毁动态查找表DT。,DestroyDSTable(,初始条件:操作结果:,动态查找表DT存在;,2019年3月2日星期六,第43页,若DT中存在其关键字等于 key的数据元素,则函数值为该元素的值或在表中的位置,否则为“空”。,SearchDSTable(DT, key)
17、;,初始条件:操作结果:,动态查找表DT存在,key 为和关键字类型相同的给 定值;,2019年3月2日星期六,第44页,动态查找表DT存在,e 为待插入的数据元素;,InsertDSTable(,初始条件:操作结果:,若DT中不存在其关键字 等于 e.key 的 数据元素, 则插入 e 到DT。,2019年3月2日星期六,第45页,动态查找表DT存在,key 为和关键字类型相同的给 定值;,DeleteDSTable(,初始条件:操作结果:,若DT中存在其关键字等于key的数据元素,则删 除之。,2019年3月2日星期六,第46页,动态查找表DT存在,Visit 是对结点操作的应用函数;,T
18、raverseDSTable(DT, Visit();,初始条件:操作结果:,按某种次序对DT的每个结 点调用函数 Visit() 一次且至 多一次。一旦 Visit() 失败, 则操作失败。,2019年3月2日星期六,第47页,(n) (1) (n) (1) (nlogn),综合上一节讨论的几种查找表的特性:,查找 插入 删除,无序顺序表 无序线性链表 有序顺序表 有序线性链表 静态查找树表,(n) (n) (logn) (n) (logn),(n) (1) (n) (1) (nlogn),2019年3月2日星期六,第48页,1)从查找性能看,最好情况能达(logn),此时要求表有序;,2)
19、从插入和删除的性能看,最好情况能达(1),此时要求存储结构是链表。,可得如下结论:,2019年3月2日星期六,第49页,一、二叉排序树(二叉查找树),二、二叉平衡树,三、B - 树,四、B+树,五、键 树,2019年3月2日星期六,第50页,一、二叉排序树 (二叉查找树),1定义,2查找算法,3插入算法,4删除算法,5查找性能的分析,2019年3月2日星期六,第51页,(1)若它的左子树不空,则左子树上所有结点的值均小于根结点的值;,1定义:,二叉排序树或者是一棵空树;或者 是具有如下特性的二叉树:,(3)它的左、右子树也都分别是二叉排序树。,(2)若它的右子树不空,则右子树上所有结点的值均大
20、于根结点的值;,2019年3月2日星期六,第52页,50,30,80,20,90,10,85,40,35,25,23,88,例如:,是二叉排序树。,66,不,2019年3月2日星期六,第53页,通常,取二叉链表作为 二叉排序树的存储结构,typedef struct BiTNode / 结点结构struct BiTNode *lchild, *rchild; / 左右孩子指针 BiTNode, *BiTree;,TElemType data;,2019年3月2日星期六,第54页,2二叉排序树的查找算法:,1)若给定值等于根结点的关键字,则查找成功; 2)若给定值小于根结点的关键字,则继续在左子
21、树上进行查找; 3)若给定值大于根结点的关键字,则继续在右子树上进行查找。,否则,,若二叉排序树为空,则查找不成功;,2019年3月2日星期六,第55页,50,30,80,20,90,85,40,35,88,32,例如:,二叉排序树,查找关键字,= 50 ,50,50,35 ,50,30,40,35,50,90 ,50,80,90,95,2019年3月2日星期六,第56页,从上述查找过程可见,,在查找过程中,生成了一条查找路径:,从根结点出发,沿着左分支或右分支逐层向下直至关键字等于给定值的结点;,或者,从根结点出发,沿着左分支或右分支逐层向下直至指针指向空树为止。,查找成功,查找不成功,20
22、19年3月2日星期六,第57页,算法描述如下:,Status SearchBST (BiTree T, KeyType key, BiTree f, BiTree / SearchBST, ,否则表明查找不成功,返回/ 指针 p 指向查找路径上访问的最后一个结点,/ 并返回函数值为FALSE, 指针 f 指向当前访问/ 的结点的双亲,其初始调用值为NULL,2019年3月2日星期六,第58页,if (!T)else if ( EQ(key, T-data.key) )else if ( LT(key, T-data.key) )else, p = f; return FALSE; / 查找不成
23、功, p = T; return TRUE; / 查找成功,SearchBST (T-lchild, key, T, p ); / 在左子树中继续查找,SearchBST (T-rchild, key, T, p ); / 在右子树中继续查找,2019年3月2日星期六,第59页,30,20,10,40,35,25,23,f,T,设 key = 48,f,T,f,T,22,p,f,T,f,T,T,T,T,f,f,f,p,2019年3月2日星期六,第60页,根据动态查找表的定义,“插入”操作在查找不成功时才进行;,3二叉排序树的插入算法,若二叉排序树为空树,则新插入的结点为新的根结点;否则,新插入
24、的结点必为一个新的叶子结点,其插入位置由查找过程得到。,2019年3月2日星期六,第61页,Status Insert BST(BiTree / Insert BST, ,2019年3月2日星期六,第62页,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 为 *p 的左孩子 else p-rch
25、ild = s; / 插入 *s 为 *p 的右孩子,return TRUE; / 插入成功,2019年3月2日星期六,第63页,(1)被删除的结点是叶子; (2)被删除的结点只有左子树或者只有右子树; (3)被删除的结点既有左子树,也有右子树。,4二叉排序树的删除算法,可分三种情况讨论:,和插入相反,删除在查找成功之后进行,并且要求在删除二叉排序树上某个结点之后,仍然保持二叉排序树的特性。,2019年3月2日星期六,第64页,50,30,80,20,90,85,40,35,88,32,(1)被删除的结点是叶子结点,例如:,被删关键字 = 20,88,其双亲结点中相应指针域的值改为“空”,20
26、19年3月2日星期六,第65页,50,30,80,20,90,85,40,35,88,32,(2)被删除的结点只有左子树 或者只有右子树,其双亲结点的相应指针域的值改为 “指向被删除结点的左子树或右子树”。,被删关键字 = 40,80,2019年3月2日星期六,第66页,50,30,80,20,90,85,40,35,88,32,(3)被删除的结点既有左子树,也有右子树,40,40,以其前驱替代之,然后再删除该前驱结点,被删结点,前驱结点,被删关键字 = 50,2019年3月2日星期六,第67页,综上所述,可以得到下面的在二叉排序树中删去一个结点的算法:,BSTNode * DelBST(BS
27、Tree t, KeyType k) /*在二叉排序树t中删去关键字为k的结点*/ BSTNode *p, *f, *s , *q; p=t; f=NULL; while(p) /*查找关键字为k的待删结点p*/ if(p-key=k ) break; /*找到, 则跳出查找循环*/f=p; /*f指向p结点的双亲结点*/if(p-keyk) p=p-lchild; else p=p-rchild; if(p=NULL) return t; /*若找不到, 返回原来的二叉排序树*/ if(p-lchild=NULL) /*p无左子树*/ if(f=NULL) t=p-rchild; /*p是原
28、二叉排序树的根*/,2019年3月2日星期六,第68页,else if(f-lchild=p) /*p是f的左孩子*/ f-lchild=p-rchild ; /*将p的右子树链到f的左链上*/ else /*p是f的右孩子*/ f-rchild=p-rchild ; /*将p的右子树链到f的右链上*/free(p); /*释放被删除的结点p*/ else /*p有左子树*/ q=p; s=p-lchild; while(s-rchild) /*在p的左子树中查找最右下结点*/q=s; s=s-rchild; if(q=p) q-lchild=s-lchild ; /*将s的左子树链到q上*/
29、else q-rchild=s-lchild; p-key=s-key; /*将s的值赋给p*/free(s); return t; /*DelBST*/ (使用方法二),【算法在二叉排序树中删除结点】,2019年3月2日星期六,第69页,5查找性能的分析,对于每一棵特定的二叉排序树,均可按照平均查找长度的定义来求它的 ASL 值,显然,由值相同的 n 个关键字,构造所得的不同形态的各棵二叉排序树的平均查找长度的值不同,甚至可能差别很大。,2019年3月2日星期六,第70页,由关键字序列 3,1,2,5,4构造而得的二叉排序树,,由关键字序列 1,2,3,4,5构造而得的二叉排序树,,例如:,
30、2,1,3,4,5,3,5,4,1,2,ASL =(1+2+3+4+5)/ 5= 3,ASL =(1+2+3+2+3)/ 5 = 2.2,2019年3月2日星期六,第71页,下面讨论平均情况:,不失一般性,假设长度为 n 的序列中有 k 个关键字小于第一个关键字,则必有 n-k-1 个关键字大于第一个关键字,由它构造的二叉排序树:,n-k-1,k,的平均查找长度是 n 和 k 的函数,P(n, k) ( 0 k n-1 )。,2019年3月2日星期六,第72页,假设 n 个关键字可能出现的 n! 种排列的可能性相同,则含 n 个关键字的二叉排序树的平均查找长度:,在等概率查找的情况下,,201
31、9年3月2日星期六,第73页,2019年3月2日星期六,第74页,由此,可类似于解差分方程,此递归方程有解:,2019年3月2日星期六,第75页,二、二叉平衡树,何谓“二叉平衡树”?,二叉平衡树的查找性能分析,如何构造“二叉平衡树”,2019年3月2日星期六,第76页,二叉平衡树是二叉查找树的另一种形式,其特点为:,树中每个结点的左、右子树深度之差的绝对值不大于1 。,例如:,5,4,8,2,5,4,8,2,1,是平衡树,不是平衡树,2019年3月2日星期六,第77页,构造二叉平衡(查找)树的方法是: 在插入过程中,采用平衡旋转技术。,例如:依次插入的关键字为5, 4, 2, 8, 6, 9,
32、5,4,2,4,2,5,8,6,6,5,8,4,2,向右旋转 一次,先向右旋转 再向左旋转,2019年3月2日星期六,第78页,4,2,6,5,8,9,6,4,2,8,9,5,向左旋转一次,继续插入关键字 9,2019年3月2日星期六,第79页, 已知一棵平衡二叉排序树如下图(a)所示。在A的左子树的左子树上插入15后,导致失衡,如下图(b)所示。为恢复平衡并保持二叉排序树的特性,可将A改为B的右子,B原来的右子改为A的左子,如下图(c)所示。这相当于以B为轴,对A做了一次顺时针旋转。(LL型),图 不平衡二叉树的调整(1),2019年3月2日星期六,第80页, 已知一棵平衡二叉排序树如下图(
33、a)所示。在A的右子树B的右子树上插入70后,导致失衡,如下图(b)所示。为恢复平衡并保持二叉排序树的特性, 可将A改为B的左子,B原来的左子改为A的右子,如下图(c)所示。这相当于以B为轴, 对A做了一次逆时针旋转。( RR型),图 不平衡二叉树的调整(2),2019年3月2日星期六,第81页, 已知一棵平衡二叉排序树如下页图(a)所示。 在A的左子树B的右子树上插入45后,导致失衡,如下页图(b)所示。为恢复平衡并保持二叉排序树的特性,可首先将B改为C的左子,而C原来的左子改为B的右子;然后将A改为C的右子, C原来的右子改为A的左子, 如下页图(c)所示。这相当于对B做了一次逆时针旋转,
34、 对A做了一次顺时针旋转。(LR型),2019年3月2日星期六,第82页,图 不平衡二叉树的调整(3),先逆时针旋转,再 顺时针旋转。(LR型),2019年3月2日星期六,第83页, 已知一棵平衡二叉排序树如下页图(a)所示。 在A的右子树的左子树上插入55后,导致失衡,如下页图(b)所示。 为恢复平衡并保持二叉排序树的特性,可首先将B改为C的右子, 而C原来的右子改为B的左子;然后将A改为C的左子,C原来的左子改为A的右子,如下页图(c)所示。这相当于对B做了一次顺时针旋转, 对A做了一次逆时针旋转。 (RL型),2019年3月2日星期六,第84页,图 不平衡二叉树的调整(4),先顺时针旋转
35、,再逆时针旋转。(RL型),2019年3月2日星期六,第85页,1) LL型(顺时针旋转),图 二叉排序树的LL型平衡旋转,2019年3月2日星期六,第86页,在一般二叉排序树的结点中增加一个存放平衡因子的域bf, 就可以用来表示平衡二叉排序树。在下面的讨论中,我们约定: 用来表示结点的字母,同时也用来表示指向该结点的指针。 因此,LL型失衡的特点是:A-bf=2,B-bf=1。 相应调整操作可用如下语句完成:,B=A-lchild; A-lchild=B-rchild; B-rchild=A; A-bf=0; B-bf=0;,2019年3月2日星期六,第87页,最后,将调整后二叉树的根结点B
36、“接到”原A处。 令A原来的父指针为FA,如果FA非空,则用B代替A做FA的左子或右子; 否则原来A就是根结点,此时应令根指针t指向B: if (FA=NULL) t=B; else if (A=FA-lchild) FA-lchild=B; else FA-rchild=B;,2019年3月2日星期六,第88页,2) LR型(先逆时针旋转,再顺时针旋转) ,图 二叉排序树的LR型平衡旋转,2019年3月2日星期六,第89页,LR型失衡的特点是:A-bf=2, B-bf=-1。 相应调整操作可用如下语句完成: B=A-lchild;C=B-rchild; B-rchild=C-lchild;
37、A-lchild=C-rchild; C-lchild=B; C-rchild=A;,2019年3月2日星期六,第90页,然后针对上述三种不同情况,修改A、B、C的平衡因子: if (S-key key) /* 在CL下插入S */ A-bf=-1; B-bf=0 ; C-bf=0; if (S-key C-key) /* 在CR下插入S */ A-bf=0; B-bf=1 ; C-bf=0; if (S-key =C-key) /* C本身就是插入的新结点S */ A-bf=0; B-bf=0 ; ,2019年3月2日星期六,第91页,最后,将调整后的二叉树的根结点C“接到”原A处。令A原来
38、的父指针为FA,如果FA非空,则用C代替A做FA的左子或右子;否则,原来A就是根结点, 此时应令根指针t指向C:if (FA=NULL) t=C; else if (A=FA-lchild) FA-lchild=C; else FA-rchild=C;,2019年3月2日星期六,第92页,3) RR型(逆时针旋转) RR型与LL型对称。假设最底层失衡结点为A, 在结点A的右子树的右子树上插入新结点S后,导致失衡,如下页图(a)所示。由A和B的平衡因子容易推知,BL、BR以及AL深度相同。为恢复平衡并保持二叉排序树特性,可将A改为B的左子,B原来的左子BL改为A的右子,如下页图(b)所示。 这相
39、当于以B为轴,对A做了一次逆时针旋转。,2019年3月2日星期六,第93页,图 二叉排序树的RR型平衡旋转,2019年3月2日星期六,第94页,RR型失衡的特点是:A-bf=-2, B-bf=-1。 相应调整操作可用如下语句完成: B=A-rchild; A-rchild=B-lchild; B-lchild=A; A-bf=0; B-bf=0;,2019年3月2日星期六,第95页,最后,将调整后二叉树的根结点B“接到”原A处。 令A原来的父指针为FA,如果FA非空,则用B代替A做FA的左子或右子; 否则, 原来A就是根结点,此时应令根指针t指向B: if (FA=NULL) t=B; els
40、e if (A=FA-lchild)FA-lchild=B; else FA-rchild=B;,2019年3月2日星期六,第96页,4) RL型(先顺时针旋转,再逆时针旋转。) ,图 二叉排序树的RL型平衡旋转,2019年3月2日星期六,第97页,RL型失衡的特点是:A-bf=-2,B-bf=1。 相应调整操作可用如下语句完成: B=A-rchild; C=B-lchild; B-lchild=C-rchild; A-rchild=C-lchild; C-lchild=A; C-rchild=B;,2019年3月2日星期六,第98页,然后针对上述三种不同情况,修改A、B、C的平衡因子: if
41、 (S-key key) /* 在CL下插入S */ A-bf=0; B-bf=-1 ; C-bf=0; if (S-key C-key) /* 在CR下插入S */ A-bf=1; B-bf=0 ; C-bf=0; if (S-key =C-key) /* C本身就是插入的新结点S */ A-bf=0; B-bf=0 ; ,2019年3月2日星期六,第99页,最后,将调整后的二叉树的根结点C“接到”原A处。 令A原来的父指针为FA,如果FA非空,则用C代替A做FA的左子或右子;否则,原来A就是根结点,此时应令根指针t指向C: if (FA=NULL) t=C; else if (A=FA-l
42、child) FA-lchild=C; else FA-rchild=C;,2019年3月2日星期六,第100页,综上所述, 在一个平衡二叉排序树上插入一个新结点S时,主要包括以下三步: (1) 查找应插位置, 同时记录离插入位置最近的可能失衡结点A(A的平衡因子不等于0)。 (2) 插入新结点S, 并修改从A到S路径上各结点的平衡因子。 (3) 根据A、 B的平衡因子, 判断是否失衡以及失衡类型, 并做相应处理。,2019年3月2日星期六,第101页,下面给出完整算法,其中AVLTree为平衡二叉排序树类型, AVLTNode为平衡二叉排序树结点类型。,void ins-AVLtree(AV
43、LTree *avlt , KeyType k) /*在平衡二叉排序树中插入元素k, 使之成为一棵新的平衡二叉排序树*/ S=(AVLTree)malloc(sizeof(AVLTNode); S-key=k; S-lchild=S-rchild=NULL; S-bf=0; if (*avlt=NULL) *avlt=S; else ,2019年3月2日星期六,第102页,/* 首先查找S的插入位置fp, 同时记录距S的插入位置最近且平衡因子不等于0(等于-1或1)的结点A, A为可能的失衡结点*/ A=*avlt; FA=NULL; p=*avlt; fp=NULL while (p! =N
44、ULL) if (p-bf! =0) A=p; FA=fp; fp=p; if (K key) p=p-lchild; else p=p-rchild; /* 插入S*/ if (K key) fp-lchild=S; else fp-rchild=S;,2019年3月2日星期六,第103页,/* 确定结点B, 并修改A的平衡因子 */ if (K key) B=A-lchild; A-bf=A-bf+1 else B=A-rchild; A-bf=A-bf-1 /* 修改B到S路径上各结点的平衡因子(原值均为0)*/ p=B; while (p! =S) if (K key) p-bf=1;
45、 p=p-lchild else p-bf=-1; p=p-rchild /* 判断失衡类型并做相应处理 */ if (A-bf=2 & B-bf=1) /* LL型 */ B=A-lchild; A-lchild=B-rchild;,2019年3月2日星期六,第104页,B-rchild=A; A-bf=0; B-bf=0; if FA=NULL *avlt=B else if A=FA-lchild FA-lchild=B else FA-rchild=B; else if (A-bf=2 & B-bf=-1) /* LR型 */ B=A-lchild; C=B-rchild; B-rch
46、ild=C-lchild; A-lchild=C-rchild; C-lchild=B; C-rchild=A;,2019年3月2日星期六,第105页,if (S-key key) A-bf=-1; B-bf=0 ; C-bf=0; else if (S-key C-key) A-bf=0; B-bf=1 ; C-bf=0; else A-bf=0; B-bf=0 ; if (FA=NULL) *avlt=C; else if (A=FA-lchild) FA-lchild=C; else FA-rchild=C; else if (A-bf=-2 & B-bf=1) /* RL型 */ ,2
47、019年3月2日星期六,第106页,B=A-rchild; C=B-lchild; B-lchild=C-rchild; A-rchild=C-lchild; C-lchild=A; C-rchild=B; if (S-key key) A-bf=0; B-bf=-1 ; C-bf=0; else if (S-key C-key) A-bf=1; B-bf=0 ; C-bf=0; else A-bf=0; B-bf=0 ; if (FA=NULL) *avlt=C; else if (A=FA-lchild) FA-lchild=C; else FA-rchild=C; ,2019年3月2日星期六,第107页,else if (A-bf=-2 else FA-rchild=B; ,