low=mid+1;
else
high=mid-1;
}
return0;
}
【性能分析】
折半查找过程可用一棵判定树来描述。
判定树中每一结点对应表中一个记录,但结点值不是记录的关键字,而是记录在表中的位置序号。
根结点对应当前区间的中间记录,左子树对应前一子表,右子树对应后一子表。
例:
对于下面有序表进行折半查找的判定树如下:
(05,13,19,21,37,56,64,75,80,88,92)
查找表中任一元素的过程,即是判定树中从根到该元素结点路径上各结点关键码的比较次数,也即该元素结点在树中的层次数。
对于n个结点的判定树,树高为k,则有k<=┗log2n┛+1。
因此,折半查找在查找成功时,所进行的关键码比较次数至多为┗log2n┛+1。
查找失败时和给定值进行比较的关键字的个数也超不过┗log2n┛+1。
折半查找成功时的平均查找长度为:
折半查找的时间复杂度为O(logn)
优点:
比较次数少,查找速度快,平均性能好。
缺点:
要求待查的表为有序顺序表,插入、删除不方便。
9.2.4索引顺序表的查找(分块查找)
索引顺序表的组织:
首先将列表分成若干个块(子表)。
一般情况下,块的长度均匀,最后一块可以不满。
每块中元素任意排列,即块内无序,但块与块之间有序。
构造一个索引表。
其中每个索引项对应一个块并记录每块的起始位置,和每块中的最大关键字(或最小关键字)。
索引表按关键字有序排列。
例:
下图为一个索引顺序表
分块查找的基本过程为:
1)首先,将待查关键字K与索引表中的关键字进行比较,以确定待查记录所在的块。
具体的可用顺序查找法或折半查找法进行。
2)进一步用顺序查找法,在相应块内查找关键字为K的元素。
分块查找的平均查找长度由两部分组成:
即查找索引表时的平均查找长度为LB,以及在相应块内进行顺序查找的平均查找长度LW。
ASLbs=LB+LW
假定将长度为n的表分成b块,且每块含s个元素,则b=n/s。
又假定表中每个元素的查找概率相等,则每个索引项的查找概率为1/b,块
中每个元素的查找概率为1/s。
若用顺序查找法确定待查元素所在的块,则有:
若用顺序查找法确定待查元素所在的块,则有:
当s=
时,ASLbs取最小值
+1。
若用顺序查找法确定待查元素所在的块,则有:
ASLbs=LB+LW=log2(b+1)/2-1+(s+1)/2
≈log2(n/s+1)+s/2
9.3.1二叉排序树
1.二叉排序树定义
二叉排序树(BinarySortTree)或者是一棵空树;或者是具有下列性质的二叉树:
(1)若左子树不空,则左子树上所有结点的值均小于根结点的值;
(2)若右子树不空,则右子树上所有结点的值均大于根结点的值。
(3)左右子树也都是二叉排序树。
二叉排序树又称为二叉查找树。
由定义可知,对二叉排序树进行中序遍历,可得到一个按关键码递增有序的序列。
例:
下面的树不是二叉排序树
2.二叉排序树上的查找
从其定义可见,二叉排序树的查找过程为:
①若查找树为空,查找失败。
②查找树非空,将给定值kx与查找树的根结点关键码比较。
③若相等,查找成功,结束查找过程,否则,
a.当kx小于根结点关键码,查找将在左子树上继续进行,转①
b.当kx大于根结点关键码,查找将在右树上继续进行,转①
例:
在下面的二叉排序树中查找关键字等于100、40的记录。
二叉排序树(查找表)的类型定义:
typedefintKeyType;//设关键字的类型可直接进行比较
typedefstruct node{
KeyType key;/*关键字的值*/
……/*其它字段的值*/
structnode *lchild,*rchild;/*左右指针*/
}BitNode,*BiTree;
递归算法:
BiTreeSearchBST(BiTreeT,KeyTypekey){
if(!
T)||(T->key==key)returnT;/*查找成功*/
elseif(keykey)
returnSearchBST(T->lchild,key);
else
returnSearchBST(T->rchild,key);
}
非递归算法:
BiTreeSearchBST(BiTreeT,KeyTypekey){
p=T;
while(P){
if(p->key==key)
returnp;/*查找成功*/
elseif(keykey)
p=p->lchild;
else
p=p->rchild;
}
returnNULL;/*查找失败*/
}/*SearchBST*/
3.二叉排序树的插入和生成
二叉排序树中插入一个结点:
设待插入结点的关键码为kx,先在二叉排序树中进行查找,若查找成功,不用插入;查找不成功时,则插入之。
因此,新插入结点一定是作为叶子结点添加上去的。
构造一棵二叉排序树则是逐个插入结点的过程。
例:
记录的关键码序列为:
63,90,70,55,67,42,98,83,10,45,58,则构造的一棵二叉排序树为:
二叉排序树中插入结点的递归算法:
voidInsertBST(BiTree&T,KeyTypekey){
if(T==NULL){
s=(BiTree)malloc(sizeof(BitNode));
s->key=key;s->lchild=NULL;s->rchild=NULL;T=s;
}
elseif(keykey)
InsertBST(T->lchild,key);
elseif(key>T->key)
InsertBST(T->rchild,key);
}
二叉排序树中插入结点的非递归算法:
voidInsertBST(BiTree&T,KeyTypekey){
p=NULL;q=T;
while(q!
=NULL){
if(kx==q->key)return;
elseif(kxkey){p=q;q=q->lchild;}
else{p=q;q=q->rchild;}
}
s=(BiTree)malloc(sizeof(BitNode));
s->key=key;s->lchild=NULL;s->rchild=NULL;
if(p==NULL)T=s;
elseif(keykey)p-lchild=key;
elsep-rchild=key;
}
创建二叉排序树的算法:
voidCreateBST(BiTree&T)
{T=NULL;
scanf("%d",&key);
while(key!
=ENDKEY)/*ENDKEY为自定义常数*/
{
InsertBST(bst,key);
scanf("%d",&key);
}
}
4.二叉排序树的删除
从二叉排序树中删除一个结点,必须保证删除后所得的二叉树仍然满足二叉排序树的性质不变。
删除操作:
首先确定被删除的结点是否在二叉排序树中。
若不在,则不做任何操作;否则,假设要删除的结点为p,结点p的双亲结点为f,并假设结点p是结点f的左孩子(右孩子的情况类似)。
下面分三种情况讨论:
(1)若p为叶结点,则可直接将其删除:
f->lchild=NULL;free(p);
(2)若p结点只有左子树,或只有右子树,则可将p的左子树或右子树直接改为其双亲结点f的左子树。
即:
f->lchild=p->lchild(或f->lchild=p->rchild);free(p);
(3)若p既有左子树,又有右子树,如下图(a),则处理的方法有两种:
方法一:
首先找到p结点在中序序列中的直接前驱s结点,如图(b)所示,然后将p的左子树改为f的左子树,而将p的右子树改为s的右子树:
f->lchild=p->lchild;s->rchild=p->rchild;free(p);结果如图(c)所示。
方法二:
首先找到p结点在中序序列中的直接前驱s结点,如图(b)所示,然后用s结点的值,替代p结点的值,再将s结点删除。
即:
原s结点的左子树改为s的双亲结点q的右子树:
p->data=s->data;q->rchild=s->lchild;free(s);结果如图(d)所示。
例:
从下图(a)的二叉排序树中依次删除11、13、插入13、删除5、删除9,画出每步操作后的二叉排序树。
5.二叉排序树的查找分析
在二叉排序树上的查找和折半查找类似,恰是走了一条从根到该结点的路径,和给定值比较的关键字个数不超过树的深度。
含有n个结点的二叉排序树的平均查找长度和树的形态有关,而树的形态和初始序列有关。
二叉排序树的各分支越均衡,树的深度浅,其平均查找长度ASL越小。
因此,二叉排序树查找的最好时间复杂度为O(log2n),最坏的时间复杂度为O(n),一般情形下,其时间复杂度大致可看成O(log2n),比顺序查找效率要好,但比二分查找要差。
9.3.2平衡二叉树
1.问题的提出
为使二叉排序树的检索效率最高,就要使二叉排序树在插入和删除后仍能维持好的形态。
2.平衡二叉树的概念
平衡二叉树(balancedbinarytree)是由阿德尔森一维尔斯和兰迪斯(Adelson-VelskiiandLandis)于1962年首先提出的,所以又称为AVL树。
一棵平衡二叉树或者是空树,或者是具有下列性质的二叉排序树:
(1)左子树与右子树高度之差的绝对值小于等于1;
(2)左子树和右子树也是平衡二叉排序树。
平衡因子BF(BalanceFactor):
结点的左子树深度与右子树深度之差。
3.非平衡二叉树的平衡处理
处理的原则:
对失去平衡的最小子树(最小不平衡子树)进行平衡化处理,使其变为平衡。
设与插入结点最近、且平衡因子的绝对值超过1的祖先结点为A,则以A为根结点的子树称为最小不平衡子树。
处理的方法:
分四种情况处理:
(1)LL型的处理(左左型)
条件:
A的左孩子的左子树上插入结点后,导致失衡。
调整方法:
(单右旋)。
即以B为轴,对A做一次顺时针旋转。
A成为B的右子树,而原来B的右子树则变成A的左子树。
(2)RR型的处理
条件:
A的右孩子的右子树上插入结点后,导致失衡。
调整方法:
(单左旋)。
即以B为轴,对A做一次逆时针旋转。
A成为B的左子树,而原来B的左子树则变成A的右子树。
(3)LR型的处理
条件:
A的左孩子的右子树上插入结点后,导致失衡。
调整方法:
(先左旋后右旋)。
即先以C为轴,对B做一次左旋,然后以C为轴,对A做一次右旋。
(4)RL型的处理
条件:
A的右孩子的左子树上插入结点后,导致失衡。
调整方法:
(先右旋后左旋)。
即先以C为轴,对B做一次右旋,然后以C为轴,对A做一次左旋。
实例:
例1:
给定关键字序列(13,24,37,90,53),构造AVL树。
例2:
给定关键字序列(5,3,4,7,6,8,2,1),构造AVL树。
为什么当平衡的二叉排序树因插入结点而失去平衡时,仅需对最小平衡子树进行平衡化处理即可?
由于平衡化处理后,以B或C为的新子树为平衡二叉树,且它的深度与插入之前以A为根的子树相同,因而不影响插入路径上的所有祖先结点的平衡度。
4.平衡二叉树查找的分析。
平衡二叉树本身就是一棵二叉排序树,故它的查找与二叉排序树完全相同。
时间复杂度为O(logn)。
9.3.3B-树和B+树
1.问题的提出
2.B-树的定义。
B-树(B树)是一种平衡的多路查找树。
一棵m阶的B-树,或是空树,或是满足以下条件的m叉树:
(1)树中每个结点至多有m棵子树;
(2)若根结点不是叶子结点,则至少有二棵子树;
(3)除根结点外的所有非终端结点至少有┌m/2┐棵子树;
(4)所有结点包含信息(n,A0,K1,A1,…Kn,An)其中Ki为关键字且有序,Ai为指向子树根结点的指针,Ai所指子树中所有结点的关键字均小于Ki+1,An所指子树中所有结点的关键字均大于Kn;
(5)所有叶子结点都出现在同一层次上,并且不带信息(为空)。
3.B-树的查找及分析。
例:
在图9.14中查找47和23的过程。
性能分析:
在B-树是进行查找包含两种基本操作:
(1)在B-树中找结点:
通常在磁盘上进行;
(2)在结点中找关键字:
在内存中进行。
因此在磁盘上进行查找的次数(即待查关键字所在结点在B-树是的层次数),是决定B-树查找效率的关键因素。
含n个关键字的m阶B-树的最大深度为logm/2((n+1)/2)+1
最坏的情况:
O(logm/2n)
4.B-树的插入。
深度为h的m阶B树,首先检索到第h层,确定插入结点位置。
(1)若被插入结点中关键码个数小于m-1,则插入。
(2)若被插入结点中关键码个数等于m-1,则引起
结点“分裂”。
例:
在下面的3阶B-树中依次插入30、26、85和7。
5.B-树的删除。
1)在最下层结点中删除一个关键字
⏹当结点中的关键字数大于m/2-1时,可直接删除(关键字Ki和Ai)。
⏹当结点中关键字数等于m/2-1时,如果其右(左)兄弟中关键字数目大于m/2-1,则需将其兄弟结点中的最小(或最大)关键字上移至双亲结点中,而将其双亲结点中大于(或小于)且紧靠该上移关键字的关键字下移至被删关键字所在的结点中。
⏹当最下层待