zoukankan      html  css  js  c++  java
  • 平衡二叉树

    简介:

      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);
        }
    }
  • 相关阅读:
    http://www.aboutyun.com/blog-61-62.html
    http://wenku.baidu.com/link?url=UGoPtZviipHzi5SDIlGx6hPFWAHTPLFXcZ7ieD15JMd81DEHqjehvphVMhqELmOK4qXR74dTT9nW8VBoApBc7Kfb1ZWrNF_i24fY1YRHVki
    君子不恤年之将衰,而忧志之有倦
    [canvas]空战游戏1.18
    [Canvas]空战游戏 已经可以玩了 1.13Playable
    [Canvas]空战游戏进阶 增加己方子弹管理类
    [Canvas]空战游戏进阶 增加爆炸管理类
    [JS]常见JS错误之一:Uncaught SyntaxError: Unexpected identifier
    [Canvas]空战游戏进阶 增加发射子弹 敌机中弹爆炸功能
    [Canvas]双方战机展示
  • 原文地址:https://www.cnblogs.com/sxw123/p/12805963.html
Copyright © 2011-2022 走看看