[置顶] 算法导论 之 平衡二叉树 - 删除 - 递归[C语言]

  • 作者:邹祁峰
  • 邮箱:[email protected]
  • 博客:http://blog.csdn.net/qifengzou
  • 日期:2013.12.20 12:00
  • 转载请注明来自"祁峰"的CSDN博客

1 前言

  在之前的博文《算法导论 之 平衡二叉树 - 插入》和《算法导论 之 平衡二叉树 - 打印》中已经给出了创建、插入、查询、打印以及销毁平衡二叉树的C语言实现过程,在此篇中出现的一些结构体、宏、枚举、函数等相关定义可以到以上两篇中找到。之所以现在才来写平衡二叉树的删除操作,主要是其过程相对比较复杂,且测试和实现过程中出现了各种各样的问题。


2 处理思路

  虽然平衡二叉树的结点删除操作的代码比较复杂,但是经过认真分析后,可以发现删除过程只有以下3种情况:[注:只要牢牢把握住以下3点,理解并实现平衡二叉树的删除操作不是太难]

2.1 被删的结点是叶子结点

处理思路:

  ①、将该结点直接从树中删除;

  ②、其父节点的子树高度的变化将导致父结点平衡因子的变化,通过向上检索并推算其父结点是否失衡;

  ③、如果其父结点未失衡,则继续向上检索推算其父结点的父结点是否失衡...如此反复②的判断,直到根结点;如果向上推算过程中发现了失衡的现象,则进行④的处理;

  ④、如果其父结点失衡,则判断是哪种失衡类型[LL、LR、RR、RL],并对其进行相应的平衡化处理。如果平衡化处理结束后,发现与原来以父节点为根结点的树的高度发生变化,则继续进行②的检索推算;如果与原来以父结点为根结点的高度一致时,则可说明父结点的父结点及祖先结点的平衡因子将不会有变化,因此可以退出处理;[注意:删除操作时的LL和RR型与插入操作的LL和RR型的判断有一点小区别,那就是删除操作中,当node->lchild或node->rchild的平衡因子为AVL_EH时,也进行LL或RR操作]

举例说明:

  假设现在要删除叶子结点D,如图1所示。首先,将D从树中删除,再判断其父结点C是否失衡,如果失衡则进行平衡化处理;再判断C的父结点B是否失衡,如果失衡则进行平衡化处理;再判断B的父结点A是否失衡,如果失衡则....依此类推,直到根结点或高度不再变化为止。

[置顶] 算法导论 之 平衡二叉树 - 删除 - 递归[C语言]_第1张图片

图1 叶子结点


2.2 被删的结点只有左子树或只有右子树

处理思路:

  ①、将左子树(右子树)替代原有结点C的位置;

  ②、结点C被删除后,则以C的父结点B为起始推算点,依此向上检索推算各结点(父、祖先)是否失衡;

  ③、如果其父结点未失衡,则继续向上检索推算其父结点的父结点是否失衡...如此反复②的判断,直到根结点;如果向上推算过程中发现了失衡的现象,则进行④的处理;

  ④、如果其父结点失衡,则判断是哪种失衡类型[LL、LR、RR、RL],并对其进行相应的平衡化处理。如果平衡化处理结束后,发现与原来以父节点为根结点的树的高度发生变化,则继续进行②的检索推算;如果与原来以父结点为根结点的高度一致时,则可说明父结点的父结点及祖先结点的平衡因子将不会有变化,因此可以退出处理;[注意:删除操作时的LL和RR型与插入操作的LL和RR型的判断有一点小区别,那就是删除操作中,当node->lchild或node->rchild的平衡因子为AVL_EH时,也进行LL或RR操作]

[置顶] 算法导论 之 平衡二叉树 - 删除 - 递归[C语言]_第2张图片

图2 只有左子树 或 只有右子树

2.3 被删的结点既有左子树又有右子树

处理思路:

  ①、找到被删结点C和替代结点R(结点C的前继结点或后继结点 —— 在此选择前继)

  ②、将替代结点R的值赋给结点C,再把替代结点R的左孩子RL替换替代结点R的位置,最后把替代结点R的空间释放掉;[注意:最终删除的是替代结点R,而之前要求被删除的结点C并未被删除 - 原因:通过修改结点C的值达到结点C和结点R的替换]

  ③、替代结点R被删除后,则以R的父结点E为起始推算点,依此向上检索推算父结点或祖先结点是否失衡;

  ④、如果其父结点未失衡,则继续向上检索推算其父结点的父结点是否失衡...如此反复③的判断,直到根结点;如果向上推算过程中发现了失衡的现象,则进行⑤的处理;

  ⑤、如果其父结点失衡,则判断是哪种失衡类型[LL、LR、RR、RL],并对其进行相应的平衡化处理。如果平衡化处理结束后,发现与原来以父节点为根结点的树的高度发生变化,则继续进行②的检索推算;如果与原来以父结点为根结点的高度一致时,则可说明父结点的父结点及祖先结点的平衡因子将不会有变化,因此可以退出处理;[注意:删除操作时的LL和RR型与插入操作的LL和RR型的判断有一点小区别,那就是删除操作中,当node->lchild或node->rchild的平衡因子为AVL_EH时,也进行LL或RR操作]

[置顶] 算法导论 之 平衡二叉树 - 删除 - 递归[C语言]_第3张图片

图3 既有左子树 又有右子树


3 代码实现

/******************************************************************************
 **函数名称: avl_delete
 **功    能: 删除key值结点(对外接口)
 **输入参数: 
 **     tree: 平衡二叉树
 **     key: 被删除的关键字
 **输出参数: NONE
 **返    回: AVL_SUCCESS:成功 AVL_FAILED:失败
 **实现描述: 
 **注意事项: 
 **作    者: # Qifeng.zou # 2013.12.19 #
 ******************************************************************************/
int avl_delete(avl_tree_t *tree, int key)
{
    bool lower = false; /* 记录高度是否降低 */

    if(NULL == tree->root)
    {
        return AVL_SUCCESS;
    }

    return _avl_delete(tree, tree->root, key, &lower);
}

代码1 删除结点(外部接口)

/******************************************************************************
 **函数名称: _avl_delete
 **功    能: 在以node为根结点的子树中删除指定的key值结点(内部接口)
 **输入参数: 
 **     tree: 平衡二叉树
 **     node: 以node为根结点的子树
 **     key: 被删除的关键字
 **输出参数: 
 **     lower: 高度是否降低
 **返    回: AVL_SUCCESS:成功 AVL_FAILED:失败
 **实现描述: 
 **注意事项: 
 **作    者: # Qifeng.zou # 2013.12.19 #
 ******************************************************************************/
int _avl_delete(avl_tree_t *tree, avl_node_t *node, int key, bool *lower)
{
    avl_node_t *parent = node->parent;

    /* 1. 查找需要被删除的结点 */
    if(key < node->key)         /* 左子树上查找 */
    {
        if(NULL == node->lchild)
        {
            return AVL_SUCCESS;
        }
        
        _avl_delete(tree, node->lchild, key, lower);
        if(true == *lower)
        {
            return avl_delete_left_balance(tree, node, lower);
        }
        return AVL_SUCCESS;
    }
    else if(key > node->key)    /* 右子树上查找 */
    {
        if(NULL == node->rchild)
        {
            return AVL_SUCCESS;
        }
        
        _avl_delete(tree, node->rchild, key, lower);
        if(true == *lower)
        {
            return avl_delete_right_balance(tree, node, lower);
        }
        return AVL_SUCCESS;
    }

    /* 2. 已找到将被删除的结点node */
    /* 2.1 右子树为空, 只需接它的左子树(叶子结点也走这) */
    if(NULL == node->rchild)
    {
        *lower = true;

        avl_instead_child(tree, parent, node, node->lchild);

        free(node), node = NULL;
        return AVL_SUCCESS;
    }
    /* 2.2 左子树空, 只需接它的右子树 */
    else if(NULL == node->lchild)
    {
        *lower = true;

        avl_instead_child(tree, parent, node, node->rchild)

        free(node), node=NULL;
        return AVL_SUCCESS;
    }

    /* 2.3 左右子树均不为空: 查找左子树最右边的结点 替换被删的结点 */
    avl_instead_and_delete(tree, node, node->lchild, lower);
    if(true == *lower)
    {
        return avl_delete_left_balance(tree, node, lower);
    }
    return AVL_SUCCESS;
}

代码2 查找并删除结点(内部接口)

/******************************************************************************
 **函数名称: avl_instead_and_delete
 **功    能: 找到替换结点, 并替换被删除的结点(内部接口)
 **输入参数: 
 **     tree: 平衡二叉树
 **     node: 将被删除的结点
 **     prev: 前继结点:此结点最右端的结点将会用来替换被删除的结点.
 **输出参数: 
 **     lower: 高度是否变化
 **返    回: AVL_SUCCESS:成功 AVL_FAILED:失败
 **实现描述: 
 **注意事项: 
 **     注:在此其实并不会删除node, 而是将prev的值给了node, 再删了prev.
 **         因为在此使用的递归算法, 如果真把node给释放了,会造成压栈的信息出现错误!
 **作    者: # Qifeng.zou # 2013.12.19 #
 ******************************************************************************/
int avl_instead_and_delete(avl_tree_t *tree, avl_node_t *node, avl_node_t *prev, bool *lower)
{
    if(NULL == instead->rchild)
    {
        *lower = true;
        
        node->key = prev->key;    /* 注: 将prev的值给了node */
        if(prev == node->lchild)
        {
            avl_set_lchild(node, prev->lchild);
            /* prev->parent == node结点可能失衡,此处理交给前栈的函数处理 */
        }
        else
        {
            avl_set_rchild(prev->parent, prev->lchild);
            /* prev的父结点可能失衡,此处理交给前栈的函数处理 */
        }
        free(prev), prev=NULL;    /* 注意: 释放的不是node, 而是释放prev */
        return AVL_SUCCESS;
    }

    avl_instead_and_delete(tree, node, prev->rchild, lower);
    if(true == *lower)
    {
        /* node的父结点可能失衡,此处理交给前栈的函数处理
            但prev可能失衡,因此必须在此自己处理 */
        avl_delete_right_balance(tree, prev, lower);
    }
    return AVL_SUCCESS;
}

代码3 替换并删除结点(内部接口)

/******************************************************************************
 **函数名称: avl_delete_left_balance
 **功    能: 结点node的左子树某结点被删除, 左高度降低后, 平衡化处理(内部接口)
 **输入参数: 
 **     tree: 平衡二叉树
 **     node: 结点node的左子树的某个结点已被删除
 **输出参数: 
 **     lower: 高度是否变化
 **返    回: AVL_SUCCESS:成功 AVL_FAILED:失败
 **实现描述: 
 **注意事项: 
 **作    者: # Qifeng.zou # 2013.12.19 #
 ******************************************************************************/
int avl_delete_left_balance(avl_tree_t *tree, avl_node_t *node, bool *lower)
{
    avl_node_t *rchild = NULL, *rlchild = NULL, *parent = node->parent;

    switch(node->bf)
    {
        case AVL_LH:    /* 左高: 左子树高度减1 树变矮 */
        {
            node->bf = AVL_EH;
            *lower = true;
            break;
        }
        case AVL_EH:    /* 等高: 左子树高度减1 树高度不变 */
        {
            node->bf = AVL_RH;
            *lower = false;
            break;
        }
        case AVL_RH:    /* 右高: 左子树高度减1 树失去平衡 */
        {
            rchild = node->rchild;
            switch(rchild->bf)
            {
                case AVL_EH:    /* RR型: 向左旋转 - 区别:插入操作时对AVL_EH不做处理 */
                case AVL_RH:    /* RR型: 向左旋转 */
                {
                    if(AVL_EH == rchild->bf)
                    {
                        *lower = false;
                        rchild->bf = LH;
                        node->bf = AVL_RH;
                    }
                    else /* AVL_RH == rchild->bf */
                    {
                        *lower = true;
                        rchild->bf = AVL_EH;
                        node->bf = AVL_EH;
                    }

                    avl_set_rchild(node, rchild->lchild);
                    avl_set_lchild(rchild, node);
                    avl_instead_child(tree, parent, node, rchild);
                    break;
                }
                case AVL_LH:    /* RL型: 先向右旋转 再向左旋转 */
                {
                    *lower = true;
                    rlchild = rchild->lchild;
                    switch(rlchild->bf)
                    {
                        case AVL_LH:
                        {
                            node->bf = AVL_EH;
                            rchild->bf = AVL_RH;
                            rlchild->bf = AVL_EH;
                            break;
                        }
                        case AVL_EH:
                        {
                            node->bf = AVL_EH;
                            rchild->bf = AVL_EH;
                            rlchild->bf = AVL_EH;
                            break;
                        }
                        case AVL_RH:
                        {
                            node->bf = AVL_LH;
                            rchild->bf = AVL_EH;
                            rlchild->bf = AVL_EH;
                            break;
                        }
                    }

                    avl_set_rchild(node, rlchild->lchild);
                    avl_set_lchild(rchild, rlchild->rchild);
                    avl_set_lchild(rlchild, node);
                    avl_set_rchild(rlchild, rchild); 

                    avl_instead_child(tree, parent, node, rlchild);
                    break;
                }
            }
            break;
        }
    }

    return AVL_SUCCESS;
}
代码4 左子树高度降低后平衡化处理

/******************************************************************************
 **函数名称: avl_delete_right_balance
 **功    能: 结点node的右子树某结点被删除, 左高度降低后, 平衡化处理(内部接口)
 **输入参数: 
 **     tree: 平衡二叉树
 **     node: 结点node的右子树的某个结点已被删除
 **输出参数: 
 **     lower: 高度是否变化
 **返    回: AVL_SUCCESS:成功 AVL_FAILED:失败
 **实现描述: 
 **注意事项: 
 **作    者: # Qifeng.zou # 2013.12.19 #
 ******************************************************************************/
int avl_delete_right_balance(avl_tree_t *tree, avl_node_t *node, bool *lower)
{
    avl_node_t *lchild = NULL, *lrchild = NULL, *parent = node->parent;

    switch(node->bf)
    {
        case AVL_LH:    /* 左高: 右子树高度减1 树失去平衡 */
        {
            lchild = node->lchild;
            switch(lchild->bf)
            {
                case AVL_EH:    /* LL型: 向右旋转 - 区别:插入操作时,对AVL_EH不做处理 */
                case AVL_LH:    /* LL型: 向右旋转 */
                {
                    if(AVL_EH == lchild->bf)
                    {
                        *lower = false;
                        lchild->bf = AVL_RH;
                        node->bf = AVL_LH;
                    }
                    else /* AVL_LH == lchild->bf */
                    {
                        *lower = true;
                        lchild->bf = AVL_EH;
                        node->bf = AVL_EH;
                    }

                    avl_set_lchild(node, lchild->rchild);
                    avl_set_rchild(lchild, node); 
                    avl_instead_child(tree, parent, node, lchild);
                    break;
                }
                case AVL_RH:    /* LR型: 先向左旋转 再向右旋转 */
                {
                    *lower = true;
                    lrchild = lchild->rchild;
                    switch(lrchild->bf)
                    {
                        case AVL_LH:
                        {
                            node->bf = AVL_RH;
                            lchild->bf = AVL_EH;
                            lrchild->bf = AVL_EH;
                            break;
                        }
                        case AVL_EH:
                        {
                            node->bf = AVL_EH;
                            lchild->bf = AVL_EH;
                            lrchild->bf = AVL_EH;
                            break;
                        }
                        case AVL_RH:
                        {
                            node->bf = AVL_EH;
                            lchild->bf = AVL_LH;
                            lrchild->bf = AVL_EH;
                            break;
                        }
                    }

                    avl_set_lchild(node, lrchild->rchild);
                    avl_set_rchild(lchild, lrchild->lchild);
                    avl_set_rchild(lrchild, node);
                    avl_set_lchild(lrchild, lchild);

                    avl_instead_child(tree, parent, node, lrchild);
                    break;
                }
            }
            break;
        }
        case AVL_EH:    /* 等高: 右子树高度减1 树高度不变 */
        {
            node->bf = AVL_LH;
            *lower = false;
            break;
        }
        case AVL_RH:    /* 右高: 右子树高度减1 树变矮 */
        {
            node->bf = AVL_EH;
            *lower = true;
            break;
        }
    }
    return AVL_SUCCESS;
}

代码5 右子树高度降低后 平衡化处理

/******************************************************************************
 **函数名称: avl_assert
 **功    能: 检测结点是否正常
 **输入参数: 
 **     node: 被检测的结点
 **输出参数: NONE
 **返    回: VOID
 **实现描述: 
 **注意事项: 
 **     注:在增加或删除结点的过程中,会有很多指针的操作,稍不谨慎,可能就会出现严重问题。
 **         随着插入或删除的过程,树的结构不断调整和变化,要想通过肉眼和GDB查看树内部结构
 **         是否正常,这将变得越来越困难。但是通过调用此函数,可快速判断出当前结点的相关
 **         指针信息是否正常,一旦出现异常,则会直接coredump,再通过GDB便可快速的找出
 **         错误代码。
 **作    者: # Qifeng.zou # 2013.12.20 #
 ******************************************************************************/
void avl_assert(const avl_node_t *node)
{
    if((NULL == node)
        || (NULL == node->parent)) 
    {
        return;
    }

    if((node->parent->lchild != node)           /* 检查父子关系是否正常 */
        && (node->parent->rchild != node)) 
    {
        assert(0);
    }

    if((node->parent == node->lchild)           /* 检查是否存在环行链表 */
        || (node->parent == node->rchild))
    {
        assert(0);
    }
}

代码6 结点检测

4 结果展示

  左图为原始平衡二叉树,随机删除多个结点后,得到右图。通过观察可知右图依然是一棵平衡二叉树。

[置顶] 算法导论 之 平衡二叉树 - 删除 - 递归[C语言]_第4张图片

图4 测试结果



你可能感兴趣的:([置顶] 算法导论 之 平衡二叉树 - 删除 - 递归[C语言])