目录
二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:
二叉排序树是递归定义的。由定义可以得出二叉排序树的一个重要性质:中序遍历一棵二叉树时可以得到一个结点值递增的有序序列。
图1.1-1 二叉排序树举例
- // 二叉排序树的二叉链表存储表示
- typedef struct
- {
- KeyType key; //关键字项
- InfoType otherinfo; //其他数据项
- }ElemType; //每个结点的数据域的类型
-
-
- typedef struct BSTNode
- {
- ElemType data; 〃每个结点的数据域包括关键字项和其他数据项
- struct BSTNode *lchild, *rchild; //左右孩子指针
- }BSTNode, *BSTree;
因为二叉排序树可以看成是一个有序表,所以在二叉排序树上进行査找和折半查找类似,也 是一个逐步缩小査找范围的过程。
图1.2-1 二叉排序树查找过程
复杂度分析:
含有n个结点的二叉排序树的平均查找长度和树的形态有关。当先后插入的关键字有 序时,构成的二叉排序树蜕变为单支树。树的深度为n,其平均査找长度为(n+1)/2,和顺序查找相同。
最好的情况是,二叉排序树的形态和折半查找的判定树相似,其平均査找长度和logn成正比。综合所有可能的情况,就平均而言,二叉排序树的平均査找长度仍然和logn是同数量级的。
图1.2-2 不同形态的二叉排序树
就维护表的有序性而言,二叉排序树更加有效,无需移动记录,只需修改指针即可完成对结点的插入和删除操作。因此,对于需要经常进行插入、删除和査找运算的表,釆用二叉排序树比较好。
二叉排序树的插入操作是以查找为基础的。要将一个关键字值为key的结点*S插入到二叉排序 树中,则需要从根结点向下查找,当树中不存在关键字等于key的结点时才进行插入。新插入的结点一定是一个新添加的叶子结点,并且是查找不成功时查找路径上访问的最后一个结点的左孩子或右孩子结点。
算法步骤:
图1.3-1 二叉排序树插入原则及步骤
复杂度分析:和查找完全相同。
二叉排序树的创建是从空的二叉排序树开始的,每输入一 个结点,经过查找操作,将新结点插入到当前二叉排序树的合适位置。
一个无序序列可以通过构造一棵二叉排序树而变成一个有序序列,就相当于在一个有序序列上插入一个记录而不需要移动其他记录。
算法步骤:
复杂度分析:
假设有n个结点,则需要n次插入操作,而插入一个结点的算法时间复杂度为O(logn),所以创建二叉排序树算法的时间复杂度为O(n*logn)。
被删除的结点可能是二叉排序树中的任何结点,删除结点后,要根据其位置不同修改其双亲结点及相关结点的指针,以保持二叉排序树的特性。
图1.5-1 在二叉排序树中删除*p步骤
首先从二叉排序树的根结点开始查找关键字为key的待删结点,如果树中不存在此结点, 则不做任何操作;否则,假设被删结点为*p(指向结点的指针为p),其双亲结点为*f (指向结点的指针为f), Pl和Pr分别表示其左子树和右子树
下面分三种情况讨论:
1.若*p结点为叶子结点,即Pl和Pr均为空树。由于删去叶子结点不破坏整棵树的结构, 则只需修改其双亲结点的指针即可。f->lchild = NULL;
2.若*p结点只有左子树Pl或者只有右子树Pr,此时只要令Pl或Pr直接成为其双亲结点*f的左子树即可。f->lchild = p->lchild;
3.若*p结点的左子树和右子树均不空。为保持其他元素之间的相对位 置不变,可以有两种处理方法:
•令*p的左子树为*f的左子树,而*p的右子树为*s的右子树
•令*p的直接前驱(或直接后继)替代*p,然后再从二叉排序树中删去它的直接前驱(或直接后继)。
图1.5-2 在二叉排序树中删除*p
图1-5-3 在二叉排序树中删除的各类情况分析
复杂度分析:
同二叉排序树插入一样,二叉排序树删除的基本过程也是查找,所以时间复杂度仍是O(logn)。
平衡二叉树或者是空树,或者是具有如下特征的二叉排序树:
(1 )左子树和右子树的深度之差的绝对值不超过1;
(2 )左子树和右子树也是平衡二叉树。
若将二叉树上结点的平衡因子定义为该结点左子树和右子树的深度之 差,则平衡二叉树上所有结点的平衡因子只可能是-1、0和1。
因为AVL树上任何结点的左右子树的深度之差都不超过1,则可以证明它的深度和logn 是同数量级的。由此,其查找的时间复杂度是O(logn)。
图2.1-1 平衡与不平衡的二叉树及结点的平衡因子
平衡二叉树的插入:(插入后仍保证平衡及二叉排序树特性)
若插入新结点破坏了平衡二叉树的平衡性,则从插入结点逆向向根结点方向,将第一个失衡结点作为新的根结点,即最小不平衡子树,然后进行调整。
图2.2-1 平衡调整分类
LL型:单向右旋——
①将A的左孩子B右旋代替A成为新的根结点
② A右旋成为B的右孩子
③而B的原右子树成为A的左孩子
图2.2-2 LL型实例
RR型:单向左旋——
①将A的右孩子B左旋代替A成为新的根结点
② A左旋成为B的左孩子
③而B的原左子树成为A的右孩子
图2.2-3 RR型实例
LR型:先左旋再右旋——
①将A的左子树左旋(以B的右孩子C为子树根)
②C右旋代替A成为新的根结点
图2.2-4 LR型实例
RL型:先右旋再左旋——
①将A的右子树右旋(以B的左孩子C为子树根)
②C右旋代替A成为新的根结点
图2.2-5 RL型实例
在平衡的二叉排序树BBST上插入一个新的数据元素e的递归算法可描述如下。
一棵m阶的B树,或为空树,或为满足下列特性的m叉树:
(1)树中每个结点至多有m棵子树;
(2)若根结点不是叶子结点,则至少有两棵子树;
(3)除根之外的所有非终端结点至少有m/2向上取整棵子树;
(4)所有的叶子结点都出现在同一层次上,并且不带信息,通常称为失败结点;
(5)所有的非终端结点最多有m - 1个关键字。
图3.1-1 B树定义
在 m 阶的B-树上,每个非终端结点可能含有:
n 个关键字 Ki(1≤ i≤n) n
n+1 个指向子树的指针 Ai(0≤i≤n)
B树特点:
结构描述:
- typedef struct BTNode {
- int keynum; // 结点中关键字个数,结点大小
- struct BTNode *parent;
- // 指向双亲结点的指针
- KeyType key[m+1]; // 关键字(0号单元不用)
- struct BTNode *ptr[m+1]; // 子树指针向量
- Record *recptr[m+1]; // 记录指针向量
- } BTNode, *BTree; // B树结点和B树的类型
查找过程:从根结点出发,沿指针搜索结点和在结点内进行顺序(或折半)查找,两个过程交叉进行。若查找成功,则返回指向被查关键字所在结点的指针和关键字在结点中的位置;若查找不成功,则返回插入位置。
将给定值key与根结点的各个关键字Ki, K2,…,Kj (1<=j
如果在自上而下的查找过程中,找到了值为炽y的关键字,则査找成功;如果直到叶子结点也未找到,则査找失败。
复杂度分析:
根据B-树的定义,第一层至少有1个结点;第二层至少有2个结点;由于除根之外的每个非终端结点至少有棵子树,则第三层至少有2个结点;……;依次类推,第h+1层 至少有2()^(h-1)个结点。而h+1层的结点为叶子结点。若m阶B树中具有N个关键字,则叶子结点即查找不成功的结点为N+1,由此有:
这就是说,在含有N个关键字的B-树上进行查找时,从根结点到关键字所在结点的路径上涉及的结点数为h
在查找不成功之后,需进行插入。显然,关键字插入的位置必定在最下层的非叶结点。
有下列几种情况:
1)插入后,该结点的关键字个数n 2)插入后,该结点的关键字个数 n=m,则需进行“结点分裂” 令 s = ,在原结点中保留(A0,K1,…… , Ks-1,As-1),建新结点(As,Ks+1,…… ,Kn,An);将(Ks,p)插入双亲结点; 3)若双亲为空,则建新的根结点 图3.3-1 插入举例1(三阶B树) 图3.3-1 插入举例2(三阶B树) 图3.3-1 插入举例3(三阶B树) 算法步骤: 和插入的考虑相反,首先必须找到待删关键字所在结点,并且要求删除之后,结点中关键字的个数不能小于-1,否则,要从其左(或右)兄弟结点“借调”关键字,若其左和右兄弟结点均无关键字可借(结点中只有最少量的关键字),则父结点下移,进行结点的“合并”。 图3.4-1 删除举例1 图3.4-2 删除举例2 一棵m阶的B+树和m阶的B-树的差异在于: (1) 有n棵子树的结点中含有n个关键字; (2) 所有的叶子结点中包含了全部关键字的信息,以及指向含这些关键字记录的指针,且叶 子结点本身依关键字的大小自小而大顺序链接; (3) 所有的非终端结点可以看成是索引部分,结点中仅含有其子树(根结点)中的最大(或最小)关键字。
例如,图所示为一棵3阶的B+树,通常在B+树上有两个头指针,一个指向根结点, 另一个指向关键字最小的叶子结点。因此,可以对B+树进行两种查找运算:一种是从最小关键字起顺序查找,另一种是从根结点开始,进行随机查找。 图4.1-1 —棵3阶的B+树 在B+树上进行随机查找、插入和删除的过程基本上与B-树类似。 查找:若非终端结点上的关键字等于给定值,并不终止,而是继续向下直到叶子结点。 因此,在B+树中,不管查找成功与否,每次査找都是走了一条从根到叶子结点的路径。B+树査 找的分析类似于B树。 在 B+ 树上,既可以进行缩小范围的查找,也可以进行顺序查找; B+树不仅能够有效地查找单个关键字,而且更适合查找某个范围内的所有关键字。例如,在 B+树上找出范围在[a, b]之间的所有关键字值。处理方法如下:通过一次查找找出关键字a,不管它是否存在,都可以到达可能出现a的叶子结点,然后在叶子结点中查找关键字值等于a或大于a的那些关键字,对于所找到的每个关键字都有一个指针指向相应的记录,这些记录的关键字在所需要的范围。如果在当前结点中没有发现大于b的关键字,就可以使用当前叶子结点的最后 一个指针找到下一个叶子结点,并继续进行同样的处理,直至在某个叶子结点中找到大于b的关键字,才停止査找。
插入和删除: 类似于B树进行,即必要时,也需要进行结点的“分裂”或“归并”。
3.4 B树的删除
四、B+树
4.1 基本概念
4.2 B+树的查找、插入和删除
在进行缩小范围的查找时,不管成功与否,都必须查到叶子结点才能结束;
若在结点内查找时,给定值≤Ki,则应继续在 Ai 所指子树中进行查找。