平衡二叉树

简介:

  AVL树本质上是一颗二叉查找树,但是它又具有以下特点:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。在AVL树中任何节点的两个子树的高度最大差别为一,所以它也被称为平衡二叉树。下面是平衡二叉树和非平衡二叉树对比的例图:

  平衡因子(bf):结点的左子树的深度减去右子树的深度,那么显然-1<=bf<=1;

AVL树的作用:

  我们知道,对于一般的二叉搜索树(Binary Search Tree),其期望高度(即为一棵平衡树时)为log2n,其各操作的时间复杂度(O(log2n))同时也由此而决定。但是,在某些极端的情况下(如在插入的序列是有序的时),二叉搜索树将退化成近似链或链,此时,其操作的时间复杂度将退化成线性的,即O(n)。我们可以通过随机化建立二叉搜索树来尽量的避免这种情况,但是在进行了多次的操作之后,由于在删除时,我们总是选择将待删除节点的后继代替它本身,这样就会造成总是右边的节点数目减少,以至于树向左偏沉。这同时也会造成树的平衡性受到破坏,提高它的操作的时间复杂度。
  例如:我们按顺序将一组数据1,2,3,4,5,6分别插入到一颗空二叉查找树和AVL树中,插入的结果如下图:
        

 

 

 

 

 

 

 

  由上图可知,同样的结点,由于插入方式不同导致树的高度也有所不同。特别是在带插入结点个数很多且正序的情况下,会导致二叉树的高度是O(N),而AVL树就不会出现这种情况,树的高度始终是O(lgN).高度越小,对树的一些基本操作的时间复杂度就会越小。这也就是我们引入AVL树的原因

AVL树的基本操作:

  AVL树的操作基本和二叉查找树一样,这里我们关注的是两个变化很大的操作:插入和删除!

  我们知道,AVL树不仅是一颗二叉查找树,它还有其他的性质。如果我们按照一般的二叉查找树的插入方式可能会破坏AVL树的平衡性。同理,在删除的时候也有可能会破坏树的平衡性,所以我们要做一些特殊的处理,包括:单旋转和双旋转!

  AVL树的插入,单旋转的第一种情况---右旋:

  由上图可知:在插入之前树是一颗AVL树,而插入之后结点T的左右子树高度差的绝对值不再 < 1,此时AVL树的平衡性被破坏,我们要对其进行旋转。由上图可知我们是在结点T的左结点的左子树上做了插入元素的操作,我们称这种情况为左左情况,我们应该进行右旋转(只需旋转一次,故是单旋转)。具体旋转步骤是:

  T向右旋转成为L的右结点,同时,Y放到T的左孩子上。这样即可得到一颗新的AVL树,旋转过程图如下:

 

  左左情况的右旋举例:

  AVL树的插入,单旋转的第一种情况---左旋:

 

   由上图可知:在插入之前树是一颗AVL树,而插入之后结点T的左右子树高度差的绝对值不再 < 1,此时AVL树的平衡性被破坏,我们要对其进行旋转。由上图可知我们是在结点T的右结点的右子树上做了插入元素的操作,我们称这种情况为右右情况,我们应该进行左旋转(只需旋转一次,故事单旋转)。具体旋转步骤是:

   T向右旋转成为R的左结点,同时,Y放到T的左孩子上。这样即可得到一颗新的AVL树,旋转过程图如下:

 

  右右情况的左旋举例:

  以上就是插入操作时的单旋转情况!我们要注意的是:谁是T谁是L,谁是R还有谁是X,Y,Z!T始终是开始不平衡的左右子树的根节点。显然L是T的左结点,R是T的右节点。X、Y、Y是子树当然也可以为NULL.NULL归NULL,但不能破坏插入时我上面所说的左左情况或者右右情况。

  AVL树的插入,双旋转的第一种情况---左右(先左后右)旋:

 

由  上图可知,我们在T结点的左结点的右子树上插入一个元素时,会使得根为T的树的左右子树高度差的绝对值不再 < 1,如果只是进行简单的右旋,得到的树仍然是不平衡的。我们应该按照如下图所示进行二次旋转:

  

  左右情况的左右旋转实例:

  AVL树的插入,双旋转的第二种情况---右左(先右后左)旋:

  由上图可知,我们在T结点的右结点的左子树上插入一个元素时,会使得根为T的树的左右子树高度差的绝对值不再 < 1,如果只是进行简单的左旋,得到的树仍然是不平衡的。我们应该按照如下图所示进行二次旋转:

  右左情况的右左旋转实例:

AVL树的插入代码实现:

1.创建结点

public class Node {
    int value;
    Node left;//左节点
    Node right;//右节点

    public Node(int value) {
        this.value = value;
    }

    /*添加节点的方法满足二叉排序树*/
    public  void add(Node node){
        if (node==null){
            return;
        }
        //判断传入节点和当前子树节点的值
        if (node.value<this.value){
            if (this.left ==null){//左子节点为空直接添加
              this.left=node;
            }else {
                this.left.add(node);//递归左子树添加
            }
        }else {//添加的节点大于当前节点的值
            if (this.right==null){
                this.right=node;
            }else {
                this.right.add(node);//递归右子树添加
            }
        }


        /*当添加一个节点后,满足平衡二叉树的条件右子树>左子树+1*/
        if (rightHeight() - leftHeight()>1){
            /*当前节点的右节点的左子树大于右子树的长度*/
            if (right!=null && right.leftHeight() > right.rightHeight()){
                right.rightRotate();//对当前接的的右子树进行右旋转...
                /*再对当前节点进行左旋*/
                leftRotate();

            }else {//直接进行右旋
                leftRotate();
            }
            return;
        }

        /*当添加一个节点后,满足平衡二叉树的条件左子树>右子树+1*/
        if (leftHeight()-rightHeight()>1){
            if (left!=null && left.rightHeight() > left.leftHeight()){

                left.leftRotate();//对当前接的的左子树进行左旋转...
                /*再对当前节点进行右旋*/
                rightRotate();
            }else {//直接进行右旋
                rightRotate();
            }
        }


    }

2.创建屁AVL树

public class AVLTree {
    private Node root;

    public Node getRoot() {
        return root;
    }

    public void setRoot(Node root) {
        this.root = root;
    }

    /*查找要删除的节点*/
    public Node search(int va1ue){
        if (root==null){
            return null;
        }else {
            return root.search(va1ue);
        }
    }


    /*查找删除节点的父节点*/
    public Node searchParent(int value){
        if (root==null){
            return null;
        }else {
            return root.searchParent(value);
        }
    }



    /**
     *
     * @description:TODO
     * @params:1.节点(当做二叉的根节点)
     * 1.删除最小节点
     * @return: 当前节点作为二叉数根节点的最小节点值
     * @author: 苏兴旺
     * @time: 2020/3/14 12:38
     */
    public  int delRightTree(Node node){
       Node targe = node;
        //一直往左边找就是最小值
        while (targe.left!=null){
            targe = targe.left;
        }
        delNode(targe.value); //指向最小节点
        return targe.value;

    }



    /*删除节点
     * 1.找待删除的节点taget
     * 2.找到待删除节点的父节点parent
     * 3.确定 taget 是父节点 左节点 右节点
     * 4.根据实际情况进行删除
     * 左节点==null
     * 右节点==null
     *
     * 第二种:删除的节点只有一个子树
     * 1.找待删除的节点taget
     * 2.找到待删除节点的父节点parent
     * 3.确定taget的子节点是左节点还是右子节点
     * 4.taget是parent左子节点还是右子节点
     * 5.如果taget有左子节点
     *    5.1 如果taget是parent左节点 parent.左 = taget.左
     *    5.2如果taget是parent右节点parent.右 = taget.左
     * 6.如果taget有右子节点
     *    6.1 如果taget是parent左节点 parent.左 = taget.右
     *   6.2如果taget是parent右节点parent.右 = taget.右
     *
     * 情况三删除有二个数的节点:
     *.1找待删除的节点taget
     * 2.找到待删除节点的父节点parent
     * 3.从taget 的右节点找到最小的节点
     * 4.用临时变量保存保存最小节点temp
     * 5.删除该节点
     * taget.value=temp*/




    public void  delNode(int value){
        if (root == null){
            return;
        }else {
            Node target = search(value);
            if (target == null){//找到删除的节点taget
                return;
            }

            //target 树只有一个root接电脑
            if (root.left==null && root.right==null){
                root=null;
                return;
            }
            //找到删除节点的父节点
            Node parent = searchParent(value);
            if (target.left==null&&target.right==null){//为叶子节点
                if (parent.left!=null &&parent.left.value==value){//判断target节点是父节点的左节点还是右节点
                    parent.left=null;
                }else if (parent.right!=null&& parent.right.value==value){
                    parent.right=null;
                }
            }else if (target.left!=null && target.right!=null){//情况三删除有二个数的节点:
                // 情况三删除有二个数的节点:
                //1.找待删除的节点taget
                //2.找到待删除节点的父节点parent
                // 3.从taget 的右节点找到最小的节点
                // 4.用临时变量保存保存最小节点temp
                // 5.删除该节点
                int minVal = delRightTree(target.right);
                target.value=minVal;
            }else {//删除的节点只有一个子树
                if (target.left!=null){//如果要删除的有左子节点
                    if (parent.left.value==value){//target是父节点的左节点
                        parent.left=target.left;
                    }else {//target是父节点的右节点
                        parent.right = target.left;
                    }
                }else {//如果要删除的有右子节点
                    if (parent.left.value==value){//target是父节点的左节点
                        parent.left=target.right;
                    }else {//target是父节点的右节点
                        parent.right=target.right;
                    }
                }
            }


        }
    }


    /*添加方法*/
    public  void  add(Node node){
        if (root==null){
            root = node;
        }else {
            root.add(node);
        }
    }


    /*中序遍历*/
    public  void infixOrder(){
        if (root==null){
            System.out.println("二叉排序树为空!!!");
        }else {
            root.infixOrder();
        }
    }


}

3.进行测试

public class ALMTreeDemo {
    public static void main(String[] args) {
        //int[] arr = {4,3,6,5,7,8};//左旋
        //int[] arr = {10,12,8,9,7,6};//右旋
        int[] arr = {10,11,7,6,8,9};
        AVLTree avlTree = new AVLTree();

        /*添加节点*/
        for (int i = 0; i < arr.length; i++) {
            avlTree.add(new Node(arr[i]));
        }
        avlTree.infixOrder();
        //在没有旋转之前
        System.out.println("没有旋转之后");
        System.out.println("数的高度:" +avlTree.getRoot().height());
        System.out.println("左子数的高度:" +avlTree.getRoot().leftHeight());
        System.out.println("右子数的高度:" +avlTree.getRoot().rightHeight());
        System.out.println("当前根节点:" +avlTree.getRoot());
        System.out.println("当前根节点的左节点:" +avlTree.getRoot().left);
        System.out.println("当前根节点的右节点:" +avlTree.getRoot().right.left);
    }
}

 

posted @ 2020-04-29 23:41  苏先生139  阅读(369)  评论(0编辑  收藏  举报