Java实现二叉树(二):平衡二叉树的实现之AVL树

Java实现二叉树(二):平衡二叉树的实现之AVL树

    前文中,我们实现了二叉查找树,同时,我们也提到了这么一个隐患:如果二叉树无法控制自己的深度,那么,二叉树的查找效率很可能会发生极端的转化–如,顺序的将一堆数据插入查找二叉树,此时,二叉树会成为一个近似链表的数据结构。所以,为了解决这个问题,我们必须要找到问题的根源所在–二叉树深度,由此,推出本文的主角–平衡二叉树。

    一、平衡二叉树的概念

    定义:平衡二叉树,要求左右子树的深度差别不超过1,且左右子树都是平衡二叉树。(二叉树的定义总是充满了递归味)

    平衡二叉树的实现方式:平衡二叉树有多种实现方式,最常见的就有AVL树以及红黑二叉树,AVL树是最早被发明的平衡二叉树,敬它是前辈,本文先说AVL树的实现。

    二、AVL树的概念

    定义在上头已经说过,咱们直接看看AVL树如何实现自平衡的吧(所谓自平衡,就是在节点发生修改的时候,自己完成树平衡的调整)。

    示例:

    假设有如下四种树:

《Java实现二叉树(二):平衡二叉树的实现之AVL树》

    要说不平衡的情况其实千变万化,比如左边深度为一万,右边深度为0,但是我们考虑的是,在平衡树中修改一个节点以后,发生的不平衡“事件”,所以,左右的差只能为2。

  1. 第一种情况,是根节点的左子树高于根节点的右子树,且,左子树的非叶子节点为左子树,此情况称之为“左左”。
    解决方案:将左子树的叶子节点–也就是他的右子树,“剪切”到根节点的左边,成为根节点的左叶子,因为他满足大于左子树且小于根节点的要求。再将原左子树变为根节点,根节点变为新右子树,平衡的同时,满足了二叉查找树的要求。
    实现步骤:
    《Java实现二叉树(二):平衡二叉树的实现之AVL树》
  2. 第二种情况,对应着图4,属于情况一的对称,就不赘述了。
  3. 第三种情况,对应图2,同样是,根节点的左子树高于根节点的右子树,但是左子树暴露出来的叶子节点十分不友好,因为他是二叉树的左叶子,此时称之为“左右”,左叶子虽然满足小于根节点的要求,但是不大于左子树,所以无法直接“剪切”。
    解决方法:将左子树完成一次逆时针右旋转,再将根节点完成一次顺时针左旋转。
    实现步骤:
    《Java实现二叉树(二):平衡二叉树的实现之AVL树》
  4. 第四情况,对应图3,同样对称,也就不说了。

    总结:其实旋转的本质,是让不平衡的更深一端,替换根节点,降低其深度,这时,更深端的子树就变成原根节点,如何处理原更深端的子树,就成了最大问题。如左左,右右,都是有一个能符合要求的叶子节点,能直接完成叶子的剪切。左右和右左,暴露出来的叶子都不能完成剪切,所以需要对(左或者右)节点来一次旋转,让上层的树有一个符合要求的叶子。

    三、Java实现

    不同于普通的二叉查找树,二叉平衡树,需要关注节点的高度,所以需要在数据结构中,加入节点高度的封装。同时,加入左旋转,右旋转,左右旋转,右左旋转的方法,并在删除节点,或者插入节点这类发生节点变动的情况,判断是否树不平衡,以及是哪种情况的不平衡,再处理旋转。Talk is cheap,我们直接看代码吧。

    左旋转代码:

//当为“左左”情况的时候,发生的单旋转
    public void singleRollLeft()
    {
        //将临时右子树设置为root
        Tree rightTree = root;
        //获取root的左子树
        Tree leftTree = root.getlChild();
        //获取原左子树的右叶子
        Tree rightOne = leftTree.getrChild();
        //将root置为左子树
        root = leftTree;
        //将右子树的左节点变为原左子树的右叶子
        rightTree.setlChild(rightOne);
        root.setrChild(rightTree);
        //高度调整,由于只有原来的左子树和原来的父节点发生了变更,所以只修改两个节点的高度
        root.getrChild().height = max(root.getrChild().getlChild().height,root.getrChild().getrChild().height)+1;
        root.height = max(root.getlChild().height,root.getrChild().height)+1;
    }

    右旋转代码:

 //当为“右右”情况的时候,发生的单旋转
    public void singleRollRight()
    {
        //将临时左子树设置为root
        Tree leftTree = root;
        //获取root的右子树
        Tree rightTree = root.getrChild();
        //获取原右子树的左叶子
        Tree leftOne = rightTree.getlChild();
        //将root置为右子树
        root = rightTree;
        //将左子树的右节点变为原右子树的左叶子
        leftTree.setrChild(leftOne);
        root.setlChild(leftTree);
        //高度调整
        root.getlChild().height = max(root.getlChild().getlChild().height,root.getlChild().getrChild().height)+1;
        root.height = max(root.getlChild().height,root.getrChild().height)+1;
    }

    左右旋转:

    //当碰到左右的情况
    public void doubleRollLR()
    {
        TreeUtil leftRoot = new TreeUtil(root.getlChild());
        leftRoot.singleRollRight();
        singleRollLeft();
    }

    右左旋转:

    //当碰到右左的情况
    public void doubleRollRL()
    {
        TreeUtil rightRoot = new TreeUtil(root.getrChild());
        rightRoot.singleRollLeft();
        singleRollRight();
    }

    插入:

 //插入
    public void insert(int value)
    {
        if(root == null)
        {
            root = new Tree();
            root.setValue(value);
        }
        else if(value == root.getValue())
        {
            return;
        }
        else if(value > root.getValue()){
            TreeUtil rightRoot = new TreeUtil(root.getrChild());
            rightRoot.insert(value);
            root.setrChild(rightRoot.getRoot());
            int leftHeight = 0;
            int rightHeight = 0;
            if(root.getrChild() != null)
            {
                rightHeight = root.getrChild().height;
            }
            if(root.getlChild() != null)
            {
                leftHeight = root.getlChild().height;
            }
            if(leftHeight+ 2 == rightHeight )
            {
                if(value < root.getrChild().getValue())
                {
                    doubleRollRL();
                }
                else{
                    singleRollRight();
                }
            }
        }
        else
        {
            TreeUtil leftRoot = new TreeUtil(root.getlChild());
            leftRoot.insert(value);
            root.setlChild(leftRoot.getRoot());
            int leftHeight = 0;
            int rightHeight = 0;
            if(root.getrChild() != null)
            {
               rightHeight = root.getrChild().height;
            }
            if(root.getlChild() != null)
            {
                leftHeight = root.getlChild().height;
            }
            if(rightHeight+ 2 == leftHeight )
            {
                if(value > root.getlChild().getValue())
                {
                    doubleRollLR();
                }
                else{
                    singleRollLeft();
                }
            }
        }
    }

    删除:

 //删除
    public void delete(int value)
    {
        if(root == null)
        {
            return;
        }
        if(value > root.getValue())
        {
            TreeUtil rightRoot = new TreeUtil(root.getrChild());
            rightRoot.delete(value);
            root.setrChild(rightRoot.getRoot());
            int leftHeight = 0;
            int rightHeight = 0;
            if(root.getrChild() != null)
            {
                rightHeight = root.getrChild().height;
            }
            if(root.getlChild() != null)
            {
                leftHeight = root.getlChild().height;
            }
            if(rightHeight+ 2 == leftHeight )
            {
                if(value > root.getlChild().getValue())
                {
                    doubleRollLR();
                }
                else{
                    singleRollLeft();
                }
            }
        }
        else if(value < root.getValue())
        {
            TreeUtil leftRoot = new TreeUtil(root.getlChild());
            leftRoot.delete(value);
            root.setlChild(leftRoot.getRoot());
            int leftHeight = 0;
            int rightHeight = 0;
            if(root.getrChild() != null)
            {
                rightHeight = root.getrChild().height;
            }
            if(root.getlChild() != null)
            {
                leftHeight = root.getlChild().height;
            }
            if(leftHeight+ 2 == rightHeight )
            {
                if(value < root.getrChild().getValue())
                {
                    doubleRollRL();
                }
                else{
                    singleRollRight();
                }
            }
        }
        else {
            //判断删除节点是否存在左子树
            boolean hasLeft = false;
            //判断删除节点是否存在右子树
            boolean hasRight = false;
            if(root.getlChild() != null)
            {
                hasLeft = true;
            }
            if(root.getrChild() != null)
            {
                hasRight = true;
            }
            if(!hasLeft && !hasRight)//不包含左子树以及右子树
            {
                root = null;
            }
            else if(!hasLeft)//只包含右子树
            {
                root = root.getrChild();
            }
            else if (!hasRight)
            {
                root = root.getlChild();
            }
            else {
                Tree rollTree = root.getrChild();
                while(rollTree.getlChild() != null)
                {
                    rollTree = rollTree.getlChild();
                }
                root.setValue(rollTree.getValue());
                TreeUtil rightRoot = new TreeUtil(root.getrChild());
                rightRoot.delete(rollTree.getValue());
                root.setrChild(rightRoot.getRoot());
            }
        }

    }

    完整代码git路径:https://github.com/liufangqi/treeTestRealOne

———-

    个人再总结:核心理解四种不平衡的状态,左左,右右,左右,右左,对应着“更深”端的位置。左左可以直接单次左旋转完成平衡,右右可以单次右旋转完成平衡,左右,需要将左子树右旋转,再左旋转,右左,需要将右子树左旋转,再右旋转。

    原文作者:AVL树
    原文地址: https://blog.csdn.net/that_is_cool/article/details/80958240
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞