/*
8.6 二叉排序树:在创建树的时候就构建一个有序的树
特点:
1.若它的左子树不空,则左子树上所有结点的值均小于它的根结构的值;
2.若它的右子树不空,则右子树上所有结点的值均大于它的根节点的值;
3.它的左、右子树也分别为二叉排序树
构建一颗二叉排序树的目的,其实并不是为了排序,而是为了提高查找和插入删除关键字的速度。不管怎么说,在一个有序数据集上的查找,
速度总是要快于无序的数据集的,而二叉排序树这种非线性的结构,也有利于插入和删除的实现。
*/
/*
8.6.1 二叉排序树查找操作
首先我们提供一个二叉树结构
*/
typedef struct BiTNode
{
//结点数据
int data;
//左右孩子指针
struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;
//然后我们来看看二叉排序树的查找是如何实现的
/*
递归查找二叉排序树T中是否存在key
指针f指向T的双亲,其初始调用值为NULL
若查找成功,则指针p指向该数据元素结点,并返回TRUE
否则指针p指向查找路径上访问的最后一个结点并返回FALSE
*/
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); //在右子树继续查找
}
/*8.6.2 二叉排序树插入操作
有了二叉排序树的查找函数,那么所谓的二叉排序树的插入,其实也就是将关键字放到树中合适位置而已。
当二叉排序树T中不存在关键字等于key的数据元素时,插入key并返回TRUE,否则返回FALSE
*/
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)
//插入s为新的根节点
*T = s;
else if(key < p->data)
//插入s为左孩子
p->lchild = s;
else
//插入孩子为右孩子
p->rchild = s;
return TRUE;
}
else
//树中已有关键字相同的结点,不再插入
return FALSE;
}
/*
8.6.3 二叉树删除操作
俗话说“请神容易送神难”,我们已经介绍了二叉排序树的查找与插入算法,但是对于二叉排序树的删除,就不那么容易,
我们不能因为删除了结点,而让这棵树变得不满足二叉树排序的特性,所以删除需要考虑多种情况。
*/
/*
根据我们对删除结点三种情况的分析:
1.叶子结点;
2.仅有左或右子树的结点;
3.左右子树都有结点,我们来看代码,下面这个算法是递归方式对二叉排序树T查找key,查找到时删除。
*/
/*
若二叉排序树T中存在关键字等于key的数据元素时,则删除该数据元素结点,并返回TRUE;否则返回FALSE
*/
Status DeleteBST(BiTree *T, int key)
{
//不存在关键字等于key的数据元素
if(!*T)
return FALSE;
else
{
//找到关键字等于key的数据元素
if (key == (*T)->data)
return Delete(T);
else if (key < (*T)->data)
return DeleteBST(&(*T)->lchild, key);
else
return DeleteBST(&(*T)->rchild, key);
}
}
/*
上边这段代码和前面的二叉排序树查找几乎完全相同,唯一的区别就在于第8行,此时执行的时Delete方法,
对当前结点进行三处操作。我们来看Delete的代码。
*/
//从二叉排序树中删除结点p,并重接它的左或右子树。
Status Delete(BiTree *p)
{
BiTree q,s;
//右子树空则只需重接它的左子树
if ((*p)->rchild == NULL)
{
q = *p;
*p = (*p)->lchild;
free(q);
}
//左子树为空则只需重接它的右子树
if((*p)->lchild == NULL)
{
q = *p;
*p = (*p)->rchild;
free(q);
}
//左右子树均不空
else
{
q = *p; s = (*p)->lchild;
//转左,然后向右到尽头(找待删结点的前驱)
while (s->rchild)
{
q = s; s = s->rchild;
}
//s指向被删除结点的直接前驱
(*p)->data = s->data;
if (q != *p)
//重接q的右子树
q->rchild = s->lchild;
else
//重接q的左子树
q->lchild = s->lchild;
free(s);
}
return TRUE;
}
/*
8.6.4 二叉排序树总结
总之,二叉排序树是以链接的方式存储,保持了链接存储结构在执行插入或删除操作时不用移动元素的优点,
只要找到合适的插入和删除位置后,仅需修改链接指针即可。插入删除的时间性能比较好。而对于二叉排序树
的查找,走的就是从根节点到要查找的结点的路径,其比较次数等于给定值的结点在二叉排序树的层数。极端
情况,最少为1次,即根节点就是要找的结点,最多也不会超过树的深度。也就是说,二叉排序树的查找性能
取决于二叉树的形状。可问题就在于,二叉排序树的形状是不确定的,有可能时极端的左倾或者右倾。
也就是说,我们希望二叉排序树是平衡的,即其深度与完全二叉树相同,均为,那么查找的时间复杂度
也就是O(logn),近似于折半查找,事实上左图也不够平衡(p580 图8-6-18),明显的左重右轻。
不平衡的最坏情况就是像p580 图8-6-18 右图的斜树,查找时间复杂度为O(n),这等同于顺序查找。
因此,如果我们希望对一个集合按二叉排序树查找,做好是把它们构建成一颗平衡的二叉排序树。这样我们就
引申出另一个问题,如何让二叉排序树平衡的问题。
*/