sheng的学习笔记-平衡二叉树(AVL)和3+4重构

二叉树细节可参考:

sheng的学习笔记-二叉树(BST)_coldstarry的博客-CSDN博客

定义

平衡二叉树,又称AVL树,用于解决二叉排序树高度不确定的情况,如果二叉排序树的子树间的高度相差太大,就会让二叉排序树操作的时间复杂度升级为O(n),为了避免这一情况,为最坏的情况做准备,就出现了平衡二叉树,使树的高度尽可能的小,其本质还是一棵二叉搜索树。

平衡二叉树的性质

  • 左子树和右子树的高度之差的绝对值小于等于1
  • 左子树和右子树也是平衡二叉树

为了方便起见,给树上的每个结点附加一个数字,给出该结点左子树与右子树的高度差,这个数字称为结点的平衡因子(BF)

平衡因子=结点左子树的高度-结点右子树的高度

 左右子树的高度差(平衡因子)< 2,因此平衡二叉树所有结点的平衡因子只能是-1、0、1,如下图,是一个平衡二叉树

等价变换

等价二叉搜索树:若两棵二叉搜索树的中序遍历序列相同,则称它们彼此等价;反之亦然。

旋转调整

由于插入或者删除元素,导致的失衡。最基本的修复手段,通过围绕特定节点的旋转,实现等价前提下的局部拓扑调整。

不一定只有一个结点失去平衡,有可能插入一个结点会让多个结点失衡。这时候找 最小的失衡子树的根节点作为失衡结点

恢复平衡

那如何去恢复平衡,使得二叉搜索树依然成立为一棵平衡树?先来看平衡调整的四种类型:

 举个例子:如第一个,当平衡二叉树为AB时,插入一个C结点,使得失衡了,失衡结点为A,此时因为C结点插入的位置为失衡结点的左孩子的左孩子,所以是LL型,以此类推。

为了恢复平衡,针对每一种都不一样,但目的都是调整为平衡的,且不能违背二叉搜索树的性质:如下图是每种失衡类型的解决方法,每个都不太一样,目的都是一样的,把key的值为中等的变为树的根,最小的放在左孩子,做大的放右孩子,通过这一目的,降低树的高度,也不用死记硬背。

如书上所说,这一操作被称为树的旋转,如LL型被称为右旋,RR型称为左旋,LR型是先左旋,再右旋,RL型先右旋再左旋。

旋转协助理解图

右旋

 左旋

LL型调整

如下,在实际情况中,调整的内容可能看起来更复杂,如下方块的表示省略了n个结点,调整的方式如下(右旋):

步骤为:

  • B结点带左子树(α和新添加的C结点)一起上升
  • A结点成为B的右子树
  • 原来的B的右子树成为A的左子树,因为A的左子树是B,上升了,所以空着的

可以看成是A右旋为B的右子树

RR型

LL型和RR型是最简单的几种情况,所以放在了前面。RR型即插入的结点位置在失衡结点的右子树的右子树中,如下图:

调整的步骤和LL的差不多

步骤为:

  • B结点和它的右子树(β和新添加的C结点)一起上升
  • A结点变为B结点的左子树
  • 原来B的左子树α变为A的右子树

可以看成是A左旋至B的左子树

LR型调整

LR型的跳转如下(左旋再右旋):

  • 首先让B和它的子树(除了C)左旋至C的左子树,把C为根的树接入A的左子树
  • 然后让A右旋,成为C的右子树 

其过程就是把中位数的C上升,成为AB的双亲

RL型调整

RL型如下(先右旋再左旋):

  • 首先让B和它的子树(除了C)右旋至C的右子树,把C为根的树接入A的右子树
  • 然后让A左旋,成为C的左子树 

和LR差不多

例题:输入关键字序列(16,3,7,11 ,9,26,18,14,15)给出AVL树

参考答案:

平衡树的3+4重构

平衡二叉树也是一颗BST树,那么BST的特点,平衡二叉树也应该具有,那么BST树又有什么特点呢,特点是有序,什么有序呢,中序遍历序列有序,也就是说BST树的中序序列是单调的,这个很容易就证明了,对根节点而已,其左子树的肯定比它本身要小,其右子树的值肯定比其值要大,而中序序列是左根右,所以BST树的中序序列肯定是单调的。这个是非常重要的性质,稍后我们将用到它。

我们设g(x)为最低的失衡结点,考察祖孙三代:g~p~v,按照中序遍历次序将其重命名为:a<b<c。

它们总共拥有互不相交的四颗(可能为空的)子树,按照中序遍历次序,将其重命名为:T0<T1<T2<T3。

此时,如果我们依然按照中序的遍历次序将这两个序列混合起来,就可以得到一个长度为7的序列。在这个序列中三个结点a,b,c,必然是镶嵌于这4棵子树之间

实际上无论是哪种具体的情况,经过这样的重命名之后,按照中序遍历的次序,必然是从T0到a,再从a到T1,再从T1到b,然后从b到T2,再从T2到c,最终由c到T3,这就是BST单调性的体现

因此我们可以统一的将这三个顶点abc以及这4棵子树,按照下面的拓扑关系直接的拼接起来,这样的一种拼接是针对于三个结点,以及下属的4棵子树而言的,所以也称作3+4重构

无论是插入还是删除,无论是单旋还是双旋,最终的效果都应该是这样一种形式。

 优缺点:

  • AVL 树同二叉搜索树相比无论查找,插入还是删除,其时间复杂度都只有 LOG(n) ;
  • 插入和删除都需要旋转操作,复杂度很高,成本仍然比较高,不具备推广优势

代码示例

平衡二叉树的代码示例(从别的地方拷贝过来的,没测过,慎用)

public class AVLTree {
    private AVLNode root;
 
    public class AVLNode {
        public int data;
        public int height;
        public AVLNode parent;
        public AVLNode left;
        public AVLNode right;
 
        public AVLNode(int data) {
            this.data = data;
            this.height = 1;
        }
 
        @Override
        public String toString() {
            return "AVLNode{" +
                    "data=" + data +
                    '}';
        }
 
        public void inOrder() {//中序遍历
            if (this.left != null) {
                this.left.inOrder();
            }
            System.out.println(this);
            if (this.right != null) {
                this.right.inOrder();
            }
        }
    }
 
    private int calcHeight(AVLNode root) {
        if (root.left == null && root.right == null) {
            return 1;
        } else if (root.right == null) {
            return root.left.height + 1;
        } else if (root.left == null) {
            return root.right.height + 1;
        } else {
            return root.left.height > root.right.height ? root.left.height + 1 : root.right.height + 1;
        }
    }
 
    private int calcBF(AVLNode root) {
        if (root == null) {
            return 0;
        } else if (root.left == null && root.right == null) {
            return 0;
        } else if (root.right == null) {
            return root.left.height;
        } else if (root.left == null) {
            return -root.right.height;
        } else {
            return root.left.height - root.right.height;
        }
    }
 
    public AVLNode leftRotate(AVLNode root) {
        AVLNode oldRoot = root;
        AVLNode newRoot = root.right;
        AVLNode parent = root.parent;
        //1.newRoot 替换 oldRoot 位置
        if (null != parent) {
            if (oldRoot.parent.data > oldRoot.data) {
                parent.left = newRoot;
            } else {
                parent.right = newRoot;
            }
        }
        newRoot.parent = parent;
        //2.重新组装 oldRoot (将 newRoot 的左子树 给 oldRoot 的右子树)
        oldRoot.right = newRoot.left;
        if (newRoot.left != null) {
            newRoot.left.parent = oldRoot;
        }
        //3. oldRoot 为 newRoot 的左子树
        newRoot.left = oldRoot;
        oldRoot.parent = newRoot;
        //刷新高度
        oldRoot.height = calcHeight(oldRoot);
        newRoot.height = calcHeight(newRoot);
        return newRoot;
    }
 
 
    public AVLNode rightRotate(AVLNode root) {
        AVLNode oldRoot = root;
        AVLNode newRoot = root.left;
        AVLNode parent = root.parent;
        //1.newRoot 替换 oldRoot 位置
        if (null != parent) {
            if (oldRoot.parent.data > oldRoot.data) {
                parent.left = newRoot;
            } else {
                parent.right = newRoot;
            }
        }
        newRoot.parent = parent;
        //2.重新组装 oldRoot (将 newRoot 的右子树 给 oldRoot 的左子树)
        oldRoot.left = newRoot.right;
        if (newRoot.right != null) {
            newRoot.right.parent = oldRoot;
        }
        //3. oldRoot 为 newRoot 的左子树
        newRoot.right = oldRoot;
        oldRoot.parent = newRoot;
        //刷新高度
        oldRoot.height = calcHeight(oldRoot);
        newRoot.height = calcHeight(newRoot);
        return newRoot;
    }
 
    public void insert(int data) {
        if (null == this.root) {
            this.root = new AVLNode(data);
            return;
        }
        this.root = insert(this.root, data);
    }
 
    public AVLNode insert(AVLNode root, int data) {
        //插入左子树
        if (data < root.data) {
            if (null == root.left) {
                root.left = new AVLNode(data);
                root.left.parent = root;
            } else {
                insert(root.left, data);
            }
        }
        //插入右子树
        else if (data > root.data) {
            if (null == root.right) {
                root.right = new AVLNode(data);
                root.right.parent = root;
            } else {
                insert(root.right, data);
            }
        }
        //刷新高度
        root.height = calcHeight(root);
        //旋转
        //1. LL 型 右旋转
        if (calcBF(root) == 2) {
            //2. LR 型 先左旋转
            if (calcBF(root.left) == -1) {
                root.left = leftRotate(root.left);
            }
            root = rightRotate(root);
        }
        //3. RR型 左旋转
        if (calcBF(root) == -2) {
            //4. RL 型 先右旋转
            if (calcBF(root.right) == 1) {
                root.right = rightRotate(root.right);
            }
            root = leftRotate(root);
        }
 
        return root;
    }
 
    public void inOrder() {
        root.inOrder();
    }
}

测试:

AVLTree tree = new AVLTree();
tree.insert(16);
tree.insert(3);
tree.insert(7);
tree.insert(11);
tree.insert(9);
tree.insert(26);
tree.insert(18);
tree.insert(14);
tree.insert(15);
 
tree.inOrder();

3+4重构函数(从别的地方拷贝过来的,没测过,慎用)

//3+4重构函数
//参数:见分析
//返回:新根
Tree connect34(Tree &a,Tree &b,Tree &c,Tree &T0,Tree &T1,Tree &T2,Tree &T3)
{
    a->lchild=T0;
    if (T0)
        T0->parent=a;
    a->rchild=T1;
    if(T1)
        T1->parent=a;
    update_depth(a);
    c->lchild=T2;
    if(T2)
        T2->parent=c;
    c->rchild=T3;
    if(T3)
        T3->parent=c;
    update_depth(c);
    b->lchild=a;
    a->parent=b;
    b->rchild=c;
    c->parent=b;
    update_depth(b);
    return b;
}

3+4重构的完整代码

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
typedef struct AVLNode *Tree;
typedef int ElementType;
struct AVLNode
{
    int depth; //深度,这里计算每个结点的深度,通过深度的比较可得出是否平衡
    Tree parent; //该结点的父节点,方便操作
    ElementType val; //结点值
    Tree lchild;
    Tree rchild;
    AVLNode(int val=0) //默认构造函数
    {
        parent=NULL;
        depth=0;
        lchild=rchild=NULL;
        this->val=val;
    }
};
Tree insert_val(Tree&,Tree,Tree);
Tree remove(Tree&,ElementType);
Tree remove_val(Tree &,Tree &);
void update_depth(Tree);
int  get_balance(Tree);
int  is_balance(Tree);
Tree *Find_Min(Tree&);
Tree connect34(Tree&,Tree&,Tree&,Tree&,Tree&,Tree&,Tree&);
Tree rotateAt(Tree&,Tree&);
void setchild(Tree &,Tree &,Tree &);
 
//向AVL树中插入val
//参数:根,插入数据value
//返回:新根结点
Tree Insert(Tree &root,ElementType val)
{
    Tree temp=NULL;
    Tree node=new AVLNode(val);
    
    //插入结点
    temp=insert_val(root,node,NULL); //调用真正的插入函数
 
    if (temp)
    {
        update_depth(temp);
        root=rotateAt(root, temp);//检查树是否该调整
    }
    else //无需插入,释放结点
        delete temp;
    return root;
}
//插入函数
//参数:根节点,待插结点,待插结点的父节点
//返回:插入结点
Tree insert_val(Tree &root,Tree node,Tree parent)
{
    if (root==NULL)
    {
        root=node;
        node->parent=parent; //设置当前结点的父结点
        return root;         //返回插入结点
    }
    if (node->val<root->val) //插左子树
        return insert_val(root->lchild, node,root);
    else if(node->val>root->val) //插右子树
        return insert_val(root->rchild, node,root);
    else //已存在该结点,停止插入操作,返回NULL
        return NULL;
}
//3+4重构函数
//参数:见分析
//返回:新根
Tree connect34(Tree &a,Tree &b,Tree &c,Tree &T0,Tree &T1,Tree &T2,Tree &T3)
{
    a->lchild=T0;
    if (T0)
        T0->parent=a;
    a->rchild=T1;
    if(T1)
        T1->parent=a;
    update_depth(a);
    c->lchild=T2;
    if(T2)
        T2->parent=c;
    c->rchild=T3;
    if(T3)
        T3->parent=c;
    update_depth(c);
    b->lchild=a;
    a->parent=b;
    b->rchild=c;
    c->parent=b;
    update_depth(b);
    return b;
}
 
Tree rotateAt(Tree &root,Tree &node)
{
    Tree son,temp;
    Tree grandson;
    int balance=0; //平衡因子
    while (node!=NULL) //检查其祖先是否需要调整,更新
    {
        update_depth(node); //更新当前结点的高度信息
        balance=is_balance(node); //获取当前结点的平衡因子情况
        if (balance>1 || balance<-1) //平衡因子超标
        {
            if (balance>1) //左子树高
            {
                if (is_balance(node->lchild)>0) //LL型
                {
                    //找祖孙三代,后面的类似
                    son=node->lchild; //找其左孩子
                    grandson=son->lchild; //找其左孩子的左孩子
                    son->parent=node->parent;  //设置更新后的son的父节点
                    temp=node;
                    //重构
                    node=connect34(grandson, son, node, grandson->lchild, grandson->rchild, son->rchild, node->rchild);
                    setchild(son, temp, node);//设置son父节点的孩子为node
                }
                else  //LR型
                {
                    son=node->lchild;
                    grandson=son->rchild;
                    grandson->parent=node->parent;
                    temp=node;
                    node=connect34(son, grandson, node, son->lchild, grandson->lchild, grandson->rchild, node->rchild);
                    setchild(grandson, temp, node); //设置grandson父节点的孩子为node
                }
            }
            else //右子树高
            {
                if (is_balance(node->rchild)<0) //RR型
                {
                    son=node->rchild;
                    grandson=son->rchild;
                    son->parent=node->parent;
                    temp=node;
                    node=connect34(node, son, grandson, node->lchild, son->lchild, grandson->lchild, grandson->rchild);
                    setchild(son, temp, node);  //设置son父节点的孩子为node
                }
                else //RL型
                {
                    son=node->rchild;
                    grandson=son->lchild;
                    grandson->parent=node->parent;
                    temp=node;
                    node=connect34(node, grandson, son, node->lchild, grandson->lchild, grandson->rchild, son->rchild);
                    
                    setchild(grandson, temp, node); //设置grandson父节点的孩子为node
 
                }
            }
            if (node->parent==NULL) //到达根结点
            {
                root=node; //设置新的根结点
                break; //退出
            }
        }
        node=node->parent; //依次找到其父节点
        
    }
    return root; //返回新根
}
void setchild(Tree &g,Tree &temp,Tree &node)
{
    if (g->parent)
    {
        if (g->parent->lchild==temp)
            g->parent->lchild=node;
        else
            g->parent->rchild=node;
    }
}
//查找最小结点
Tree *Find_Min(Tree &root)
{
    if (root->lchild)
    {
       return Find_Min(root->lchild);
    }
    return &root;
}
 
//删除操作
//参数:根,需要删除的结点
//返回值: 返回删除结点的父节点
Tree remove_val(Tree &root,Tree &node)
{
    Tree parent=node->parent;
    Tree temp=NULL;
    //只有左孩子
    if (node->rchild==NULL && node->lchild!=NULL)
    {
        temp=node;
        node=node->lchild; //指向左孩子
        node->parent=temp->parent;
        delete temp;       //释放结点
        update_depth(node); //更新当前结点信息
    }
    else if(node->lchild==NULL && node->rchild!=NULL) //只有右孩子
    {
        temp=node;
        node=node->rchild; //指向右结点
        node->parent=temp->parent;
        delete temp;       //释放结点
        update_depth(node); //更新当前结点信息
    }
    else if(node->rchild==NULL && node->lchild==NULL) //叶子结点
    {
        parent=node->parent; //找到其父节点
        if (parent) //如果父节点存在
        {
            delete node;
            node=NULL;
            update_depth(parent); //更新父节点高度信息
        }
        else //删除的是根
        {
            delete root;
            root=NULL;
        }
    }
    else //既有左孩子也有右孩子,化繁为简
    {
        Tree *tmp=Find_Min(node->rchild); //找到替代元素,temp为叶子结点
        node->val=(*tmp)->val;         //更新值
        //判断当前叶子结点是左孩子还是右孩子。
        parent=(*tmp)->parent;
        delete *tmp;
        *tmp=NULL;
        update_depth(parent);
    }
    return parent;
}
 
//找到删除的结点,执行删除操作,并根据情况调整AVL树
//参数:根,需要删除的val
//返回:找到删除结点的情况则返回新根,否则返回NULL
Tree remove(Tree &root,ElementType val)
{
    static Tree *temp=NULL;
    if (root==NULL)
    {
        temp=NULL;
        return NULL;
    }
    else if(root->val<val) //在右子树查找
        remove(root->rchild, val);
    else if(root->val>val) //在左子树查找
        remove(root->lchild, val);
    else   //找到了,标记一下
       temp=&root;
    
    if (temp)
    {
        if (!root->parent) //如果已经返回到最后一次(也就是root是真正的树根)
        {
            Tree tmp=NULL;
            tmp=remove_val(root,*temp);  //执行删除操作
            return rotateAt(root, tmp);
        }
        return *temp;
    }
    return NULL;
}
 
//获取当前结点的深度
int get_balance(Tree node)
{
    if (node==NULL)
        return 0;
    return node->depth;
}
//返回当前平衡因子
int is_balance(Tree node)
{
    if (node==NULL)
        return 0;
    else
        return get_balance(node->lchild)-get_balance(node->rchild);
}
 
//更新当前深度
void update_depth(Tree node)
{
    if (node==NULL)
        return;
    else
    {
        int depth_Lchild=get_balance(node->lchild); //左孩子深度
        int depth_Rchild=get_balance(node->rchild); //右孩子深度
        node->depth=max(depth_Lchild,depth_Rchild)+1;
    }
}
//前序
void PreOrder(Tree root)
{
    if (root==NULL)
        return;
    printf("%d ",root->val);
    PreOrder(root->lchild);
    PreOrder(root->rchild);
}
//中序
void InOrder(Tree root)
{
    if (root==NULL)
        return;
    InOrder(root->lchild);
    printf("%d ",root->val);
    InOrder(root->rchild);
}
int main()
{
    Tree root=NULL;
    root = Insert(root, 16);
    root = Insert(root, 3);
    
    //插入7后LR调整
    root = Insert(root, 7);
    root = Insert(root, 11);
    
    //插入9后LL调整
    root = Insert(root, 9);
    
    //插入26后RR调整
    root = Insert(root, 26);
    
    //插入18后RL调整
    root = Insert(root, 18);
    root = Insert(root, 14);
    
    //插入15后LR调整
    root = Insert(root, 15);
    
    printf("插入:\n");
    printf("前序:");
    PreOrder(root);   // 11 7 3 9 18 15 14 16 26
    printf("\n");
    
    
    printf("中序:");
    InOrder(root);   // 3 7 9 11 14 15 16 18 26
    printf("\n");
    
    
    printf("删除:\n");
    
    //测试删除叶子结点
//    remove(root, 16);
    
    
   //测试删除只有左孩子的结点
//    remove(root, 16);
//    remove(root, 15);
    
    
    //测试删除只有右孩子的结点
//    remove(root, 14);
//    remove(root, 15);
    
    //测试删除有左右孩子的结点
//    remove(root, 18);
    
    //删除26后进行LR型调整
      remove(root, 26);
    
    
    //删除18后进行RR型
     remove(root, 18);
    
    
     remove(root, 3);
     remove(root, 9);
    
    //删除7过进行RL调整
     remove(root, 7);
    
    
    //删除11后进行LL调整
     remove(root, 11);
    
    //把结点删除完
//     remove(root, 15);
//     remove(root, 14);
//     remove(root, 16);
    
    printf("前序:");
    PreOrder(root);
    printf("\n");
    printf("中序:");
    InOrder(root);
    printf("\n");
    return 0;
}


参考文章:

平衡二叉树(AVL)图解与实现_zthgreat的博客-CSDN博客_平衡二叉树

数据结构系列(2)之 AVL 树 - 走看看

AVL树插入删除后的统一平衡算法(3+4重构)原版_墨尔基阿德斯的博客-CSDN博客

平衡二叉树_清风拂来水波不兴的博客-CSDN博客_平衡二叉树

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值