来学算法 #6 二叉搜索树(2)

接下来,在成功创建一个二叉查找树之后,我们需要通过一些操作方法对二叉查找树进行维护。这里给出二叉查找树的求树高,查找,遍历和删除操作。
一、 求树高
由于二叉查找树是递归定义的,我们不难用递归的方法求出其树高。

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;
}

这样,我们便完成了二叉搜索树的基本维护操作。
现在,我们需要注意到的是,二叉搜索树无法避免“刁钻”情况的出现,如下图二叉搜索树:
在这里插入图片描述
与其说它是个二叉搜索树,不如说它是“退化”了的链表。那么有没有办法解决这种“退化”的行为呢?接下来的其他树会完成这一工作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值