《大话数据结构》
二叉排序树
二叉排序树(Binary Sort Tree),又称为二叉查找树。它或者是一棵空树,或者是具有下列性质的二叉树。
■若它的左子树不空,则左子树上所有结点的值均小于它的根结构的值;·
■若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
■它的左、右子树也分别为二叉排序树。
构造一棵二叉排序树的目的,其实并不是为了排序,而是为了提高查找和插入删除关键字的速度。不管怎么说,在一个有序数据集上的查找,速度总是要快于无序的数据集的,而二叉排序树这种非线性的结构,也有利于插入和删除的实现。
查找
/*递归查找二叉排序树T中是否存在key,*/
/*指针f指向T的双亲,其初始调用值为NULL*/
/*若查找成功,则指针p指向该数据元素结点,并返回TRUE*/ /*否则指针p指向查找路径上访问的最后一个结点并返回FALSE*/
typedef struct BiTNode {
int data;
struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;
Status SearchBST(BiTree T, int key, BiTree f, BiTree *p) {
if (!T) /*查找不成功*/
{
*p = f;
return FALSE;
}
else if (key == T->data) /*查找成功*/
{
*p = T;
return TRUE;
}
else if (key < T->data)
return SearchBST(T->lchild, key, T, p); /*在左子树继续查找*/
else
return SearchBST(T->rchild, key, T, p); /*在右子树继续查找*/
}
插入
Status InsertBST(BiTree *T, int key) {
BiTree p, s;
if (!SearchBST(*T, key, NULL, &p)) /*查找不成功 */
{
s = (BiTree)malloc(sizeof(BiTNode));
s->data = key;
s->lchild = s->rchild = NULL;
if (!p)
*T = s; /*插入s为新的根结点*/
else if (key < p->data)
p->lchild = s; /*插入s为左孩子*/
else
p->rchild = s; /*插入s为右孩子 */
return TRUE;
}
else
return FALSE;
/*树中已有关键字相同的结点,不再插入*/
}
删除
对于要删除的结点只有左子树或只有右子树的情况,相对也比较好解决。那就是结点删除后,将它的左子树或右子树整个移动到删除结点的位置即可,可以理解为独子继承父业。比如图8-6-9,就是先删除35和99结点,再删除58结点的变化图,
但是对于要删除的结点既有左子树又有右子树的情况怎么办呢?比如图8-6-10中的47结点若要删除了,它的两儿子以及子孙们怎么办呢?
若直接删除47节点,然后把子树的节点重新插入,这么做效率很低且不说,还会导致整个二叉排序树结构发生很大的变化,有可能会增加树的高度。
因此,比较好的办法就是,找到需要删除的结点p的直接前驱(或直接后继)s,用s来替换结点p,然后再删除此结点s,如图8-6-12所示。
平衡二叉数
平衡二叉树(Self-Balancing Binary Search Tree或 Height-Balanced Binary SearchTree),是一种二叉排序树,其中每一个节点的左子树和右子树的高度差至多等于1。
不平衡的最坏情况就是像斜树,查找时间复杂度为O(n),这等同于顺序查找。因此,如果我们希望对一个集合按二叉排序树查找,最好是把它构建成一棵平衡的二叉排序树。这样我们就引申出另一个问题,如何让二叉排序树平衡的问题。
右旋操作:
/*对以p为根的二叉排序树作右旋处理*/
/*处理之后p指向新的树根结点,即旋转处理之前的左子树的根结点*/
void R_Rotate(BiTree *P) {
BiTree L;
L = (*P)->lchild; /*工指向P的左子树根结点*/
(*P)->lchild = L->rchild; /*L的右子树挂接为卫的左子树*/
L->rchild - (*P);
*P = L; /*卫指向新的根结点*/
}
左旋操作:
/*对以p为根的二叉排序树作左旋处理*/
/*处理之后p指向新的树根结点,即旋转处理之前的右子树的根结点*/
void L_Rotate(BiTree *P) {
BiTree R;
R = (*P)->rchild; /*工指向P的左子树根结点*/
(*P)->rchild = R->lchild; /*L的右子树挂接为卫的左子树*/
R->lchild - (*P);
*P = R; /*卫指向新的根结点*/
}
两次旋转情况:
当增加结点10时,结构无变化,如图8-7-7的图10。再增加结点9,此时结点7的BF变成了-2,理论上我们只需要旋转最小不平衡子树7、9、10即可,但是如果左旋转后,结点9就成了10的右孩子,这是不符合二叉排序树的特性的,此时不能简单的左旋,如图11所示。
仔细观察图11,发现根本原因在于结点7的BF是-2,而结点10的BF是1,也就是说,它们俩一正一负,符号并不统一,而前面的几次旋转,无论左还是右旋,最小不平衡子树的根结点与它的子结点符号都是相同的。这就是不能直接旋转的关键。那怎么办呢?
不统一,不统一就把它们先转到符号统一再说,于是我们先对结点9和结点10进行右旋,使得结点10成了9的右子树,结点9的BF为-1,此时就与结点7的BF值符号统一了,如图8-7-7的图12所示。
这样我们再以结点7为最小不平衡子树进行左旋,得到图8-7-8的图13。接着插入8,情况与刚才类似,结点6的BF是-2,而它的右孩子9的BF是1,如图14,因此首先以9为根结点,进行右旋,得到图15,此时结点6和结点7的符号都是负,再以6为根结点左旋,最终得到最后的平衡二叉树,如图8-7-8的图16所示。