平衡二叉树(AVL)插入以及相关旋转大法


如发现不足之处或者差错,欢迎指出,大家共同进步!!!

平衡搜索二叉树(AVL)

  • 特性
    1. 与搜索树类似,满足 根节点的值 >左子树的值 && 根节点的值< 右子树的值
    2. 子树的高度差 <= 1

例如下图

AVL

思考: 我们知道一般树的相关操作通常 为 O(logn),AVL 也一样,不同的是 AVL 的 插入 操作有些奇特: 插入一个节点可能破坏其特性,那么我们如何来完成对特性的修复呢?

方法:旋转大法

  1. 单旋转
  2. 双旋转

一、观察破坏特性的情形以及相应的解决方案

​ 我们把必须重新平衡的节点称之为 α 。对于任意二叉树的节点来说,每个节点至多有两个孩子,故当插入一个新的节点,高度不平衡的情形为 左右子树高度差 为 2,容易看出分为以下四种情形:


1.1 情形

① ‘’ 外边 ‘’
  1. 对 α 的左孩子 的 左子树 插入
  2. 对 α 的右孩子 的 右子树 插入
② ‘’ 内边 ‘’
  1. 对 α 的左孩子 的 右子树 插入
  2. 对 α 的右孩子 的 左子树 插入

可以看出,上述情形 两两呈镜面对称。

  1. 对于 ”外边“,我们采用 单旋
  2. 对于 ”内边“,我们采用 双旋

1.2 解决方案

① 单旋转
  1. 对 α 的左孩子 的 左子树 插入

​ 如图,插入节点后, 节点 k2 的 左子树的深度 比 右子树的深度 多 2 层,为使树恢复平衡,我们需要 把 x上移一层,而把 z下移一层。那么如何实现移动呢?通俗来说,就是重新构造一棵 新的满足条件的 AVL。

​ 我们知道 在中序遍历中, k1的右孩子 必比 k1 大,而比 k2 小,即 k1 的右孩子为 k1 的后继,k2 的前驱,k1 本身又是比 k2 小的左孩子。故此,只需将 k1 作为新的根,k2作为k1的右子树,k1的右孩子作为 k2的左子树即可。

正如下图那样,经过重新构造后,我们得到了一棵新的AVL。旋转大法第一式完成!😃

case1

  1. 对 α 的右孩子 的 右子树 插入

​ 同上,插入新的节点后,节点 k2的 右子树的深度 比 左子树的深度 多 2 层,为使树恢复平衡,我们需要把 x上移一层,而把 z 下移一层。

​ 不同的是,由于镜像对称 ,我们此时,用 k1 作为新的根,而k2作为 k1的左子树,k1的左孩子作为k2的右子树即可,具体原理与上述类似。旋转大法第二式完成!😺

case2

② 双旋转

引言:当我们尝试对 "内边"插入节点后的树按照 单旋转 的思路 进行重新构造树,会发现,单旋转并不能减低它的深度。正如下图那样,因为 k1的 右孩子 y 太深了。故此,我们需要另辟蹊径,重新设计算法分析。具体情况如何,且听以下分解😏

case3

  1. 对 α 的左孩子 的 右子树 插入

​ 在上图中,我们已经对子树 y 插入了一个新的节点,这保证它是非空的状态。因此,我们不妨假设它有 一个根 和两棵子树。于是可以把整棵是看成是四棵子树由 3 个节点连接。如下图所示,恰好 树 B 或 树 C 中有 一棵 比树 D 深两层(除非它们都是空的),但我们不能具体是哪一棵。

​ 为了重新平衡,我们不能继续让 k2 作为根了。又由 引言 所知,k1 和 k2 之间的旋转已经解决不了问题了,惟一的选择只能是把 k3 作为新的根,迫使 k1 是 k3 的左孩子,k2是 k3 的右孩子,k3 的右孩子作为 k2 的左孩子,从而完全确定树的最终结构。结果如下图所示。

case3

  1. 对 α 的右孩子 的 左子树 插入

​ 同上,插入新的节点后,节点 k2的 右子树的深度 比 左子树的深度 多 2 层,为使树恢复平衡,我们不能再让 k2 作为根了。同样由于镜像对称,我们迫使 k1作为 k3 的 右孩子,而 k2 作为 k3 的左孩子,k3 的 左孩子作为 k2 的右孩子,从而完全确定树的最终结构。结果如下图所示。

case4

总结

  1. 我们递归插入一个节点到 AVL 中,如果 高度 不变,那么插入完成
  2. 若高度 改变,则 区分 四种 情形,并做相应的适当的旋转分析

二、代码块解析

2.1 前期声明

  1. 为获取 树的高度差,我们有多种方法,这里我采用 一个计算并返回树的函数 :getHight ( )

  2. 一个插入的函数 :insert ( )

  3. 以及相应的旋转 函数 :

    • “ 外边”

    1.左-左 型:singleRotateWithLeft ( )

    2.右-右 型:singleRotateWithRight ( )

    • “内边”

    3.左-右 型:doubleRotateWithLeft ( )

    4.右-左 型:doubleRotateWithRight ( )


2.2 声明

1. 获取 树高度 函数
int getHight(struct AVLTreeNode *root)
{
    /*如果当前层无节点,说明无高度,故返回 0 */
    if(root==NULL)
        return 0;
    
    /*获取左右子树的高度*/
    int left=getHight(root->left);
    int right=getHight(root->right);
    
    return fmax(left,right)+1;/*或用三目运算符代替,因当前层不为空,说明高度应加 1 */
}
2. 插入函数
struct AVLTreeNode *insert(struct AVLTreeNode *root,ElmType x)
{	
    if(root==NULL)/*如果树为空,则创建一棵节点为一的 AVL,并返回*/
    {	
       root=(struct AVLTreeNode *)malloc(sizeof(struct AVLTreeNode));
       if(root==NULL)
           printf("out of space\n");
       else
       {
           root->val=x;
           root->left=root->right=NULL;
       }
        
    }
    else if(root->val>x)/* x 插入为 root 的左子树 */
    {
        root->left=insert(root->left,x);
        if(getHight(root->left)-getHight(root->right)==2)
            if(root->left->val>x)/* case 1 : 左-左 型 (外边) */
                root=singleRotateWithLeft(root);
            else/* case 3 : 左-右 型 (内边) */
                root=doubleRotateWithLeft(root);
           
    }
    else if(root->val<x)/* x 插入为 root 的右子树 */
    {
        root->right(root->right,x);
        if(getHight(root->right)-getHight(root->left)==2)
            if(root->right->val<x)/*case 2 : 右-右 型 (外边) */
                root=singleRotateWithRight(root);
            else /* case 4 : 右-左 型 (内边) */
                root=doubleRotateWithRight(root);
    }
    /* else : 插入的 元素 x 已经在 AVL 树里,我们什么都不用做 */
    
    return root;/*返回 AVL*/
    
}
3. 相关旋转函数
  • 单旋转

    1. 左-左 型

    case1

    static struct AVLTreeNode *singleRotateWithLeft(struct AVLTreeNode *k2)
    {
        struct AVLTreeNode *k1;
        
        k1=k2->left;/* k1一开始为 k2 的左子树 */
        
        /*开始旋转*/
        k2->left=k1->right;/* k1 的右孩子作为 k2 的左子树*/
        k1->right=k2;/* k2 作为 k1 的左右子树,让 k1 成为新的根 */
        
        return k1;/* 返回 以 k1 作为根的新AVL*/
    }
    
    1. 右-右 型

    case2

    static struct AVLTreeNode *singleRotateWithRight(struct AVLTreeNode *k2)
    {
        struct AVLTreeNode *k1;
        
        k1=k2->right;/* k1一开始为 k2 的右子树 */
        
        /*开始旋转*/
        k2->right=k1->left;/* k1 的左孩子作为 k2 的右子树*/
        k1->left=k2;/* k2 作为 k1 的左子树,让 k1 成为新的根 */
        
        return k1;/* 返回 以 k1 作为根的新AVL*/
    }
    
  • 双旋转

    1. 左-右 型

    case3

    static struct AVLTreeNode *doubleRotateWithLeft(struct AVLTreeNode *k2)
    {
        k2->left=singleRotateWithRight(k2->left);/* 在 k1 和 k3 间进行旋转 ,将 k3 作为 左子树 的根*/
        
        return singleRotateWithLeft(k2);/* 在 k3 和 k2 间进行旋转,将k3作为 整棵树 的根 */
    }
    
    1. 右-左 型

    case4

    static struct AVLTreeNode *doubleRotateWithRight(struct AVLTreeNode *k2)
    {
        k2->right=singleRotateWithLeft(k2->right);/* 在 k1 和 k3 间进行旋转 ,将 k3 作为 右子树 的根*/
        
        return singleRotateWithRight(k2);/* 在 k3 和 k2 间进行旋转,将k3作为 整棵树 的根 */
    }
    

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值