接下来,在成功创建一个二叉查找树之后,我们需要通过一些操作方法对二叉查找树进行维护。这里给出二叉查找树的求树高,查找,遍历和删除操作。
一、 求树高
由于二叉查找树是递归定义的,我们不难用递归的方法求出其树高。
int height(ptrNode ptr)
{
int lch, rch; //左右子树树高
if (ptr == NULL) //如果此时为空指针,则直接返回0
{
return 0;
}
lch = height(ptr->lch); //使用递归的方法对每个子树进行操作
rch = height(ptr->rch);
return (lch > rch ? lch : rch) + 1; //每向下进行一层,树高便加一。同时需要取其中的更大值为高。
二、 查找
同样的,这一操作也可以通过递归实现。
ptrNode find(ptrNode ptr, int data)
{
ptrNode temp = NULL;
if (ptr->data == data)
{
return ptr; //如果找到了该值,直接返回
}
if (ptr->data > data) //根据二叉查找的特点,只需要判断大小关系,并对一边进行查找。
{
if (ptr->lch != NULL) //不为空则向该子树查找。
{
temp = find(ptr->lch, data);
}
}
else
{
if (ptr->rch != NULL)
{
temp = find(ptr->rch, data);
}
}
return temp;
}
三、 遍历
二叉查找树主要有前序遍历,中序遍历和后序遍历三种遍历方法。这里我们以中序遍历为例。
void preordertravelsal(ptrNode ptr)
{
if (ptr != NULL)
{
preordertravelsal(ptr->lch);
printf("%d\n", ptr->data);
preordertravelsal(ptr->rch);
}
}
我们不难发现,中序遍历的输出操作位于两步递归操作的中间,同样,我们便不难理解前序遍历和后续遍历的情况了。通过分析,我们能得出,前序遍历是以根、左节点、右节点的顺序进行的。同理有中序遍历是以左节点、根节点、右节点的顺序进行的。
此外,我们给出一个二叉查找树,不难发现中序遍历有其特殊性质。
对于这个二叉查找树,其中序遍历结果为4、6、10、12、14、20。更为普遍的结论是,二叉搜索树的中序遍历实际上是由小到大进行的。实际上,这种遍历方式与堆排序有紧密的联系。
四、 删除
同许多数据结构一样,二叉查找树的删除节点工作较为复杂。
首先我们分析:需要删除的节点分为四种情况:无左右子节点、有左子节点无右子节点、有右子结点无左子结点、有左右子节点。
对于无左右子节点的情况,直接删除即可。
对只有一个子节点的两种情况,只需要将要删除的节点的子结点接到其父结点上,再删除即可。
而对于有两个子结点的情况,较为复杂,其中常用的一种方法是递归实现。
ptrNode deletedata_(ptrNode ptr, int data)
{
if (ptr == NULL)
{
return NULL; //如果为空,直接返回
}
if (ptr->data > data)
{
ptr->lch = deleteNode(ptr->lch, data); //根据二叉搜索的特点,选择不同的子树进行查找并删除,注意这里把删除的结果直接赋给了该节点的左子树,方便递归
return ptr;
}
if (ptr->data < data)
{
ptr->rch = deletedata_(ptr->rch, data);
return ptr;
}
if (ptr->lch == NULL && ptr->rch == NULL) //在满足找到需要被删除的数据后,开始判断第一种情况,即左右子树均为空
{
free(ptr);
return NULL; //直接返回空指针即可
}
if (ptr->lch == NULL && ptr->rch != NULL) //若左空而右不空,那么直接返回右子树,相当于直接接到其父亲上
{
free(ptr);
return ptr->rch;
}
if (ptr->lch != NULL && ptr->rch == NULL) //另外一种相对应的情况
{
free(ptr);
return ptr->lch;
}
ptrNode temp = ptr->rch; //第三种情况:左右均不空,此时可以用其左子树的最大值或右子树的最小值取代该位置,此处选择了右子树的最小值。
while (temp->lch != NULL)
{
temp = temp->lch;
}
int replace = temp->data; //replace为要替换被删除节点的数据的子树的数据
ptr->data = replace; //此处选择了直接以子树的数据替换需要被删除的节点的数据,再在其左子树中找到这个数据并删除
ptr->lch = deletedata_(ptr->lch, replace); //因为replace数据对应的节点必只有一个节点,故此处不需要加free函数。
return ptr;
}
这样,我们便完成了二叉搜索树的基本维护操作。
现在,我们需要注意到的是,二叉搜索树无法避免“刁钻”情况的出现,如下图二叉搜索树:
与其说它是个二叉搜索树,不如说它是“退化”了的链表。那么有没有办法解决这种“退化”的行为呢?接下来的其他树会完成这一工作。