平衡二叉树(AVL树)

目录

平衡二叉树的引入

平衡二叉树的基本概念

平衡二叉树的高度求解

平衡二叉树的创建(节点的添加插入)

左旋

右旋​​​​​​​

平衡二叉树节点的删除、查询、遍历

平衡二叉树完整汇总代码


平衡二叉树的引入

给一个数列{1,2,3,4,5,6},要求创建一颗二叉排序树(BST),并分析问题所在。

问题分析:

该二叉排序树的左子树全部为空,从形式上看更像一个单链表。插入与删除的速度不受影响,但是,查询的速度明显较低(因为要依次进行比较,每次还需要比较左子树),查询速度明显低于单链表。

解决方法:使用平衡二叉树(AVL树)

平衡二叉树的基本概念

1.平衡二叉树也称平衡二叉搜索树或者AVL树,可以保证查询效率较高。

2.平衡二叉树的前提是一个二叉排序树(二叉搜索树)。

3.平衡二叉树的特点:它是一棵空树或者它的左右两个子树的高度差的绝对值不超过1,并且它的左右两颗子树也都是一颗平衡二叉树。

4.平衡二叉树的常用的实现方法:红黑树、AVL、替罪羊树、Treap、伸展树等。

平衡二叉树的高度求解

//返回当前结点的左子树的高度
    public int leftHeight() {
        if(left != null) {
            return left.height();
        } else {
            return 0;
        }
    }
    
    //返回当前结点的右子树的高度
    public int rightHeight() {
        if(right != null) {
            return right.height();
        } else {
            return 0;
        }
    }
    
    //返回以该结点为根结点的树的高度
    public int height() {
        return Math.max(left == null ? 0 : left.height(),right == null ? 0 : right.height()) + 1;
    }

平衡二叉树的创建(节点的添加插入)

        给定某一数列,要求根据此数列创建一个平衡二叉树。当依次向树中添加某一节点后,左子树和右子树的高度差的绝对值 > 1,则此时就不再是一个平衡二叉树。如何处理?

左旋

在添加节点过程中会出现:

         添加完某一节点后,发现此时左子树的高度 - 右子树的高度 > 1,此时如何解决?

进行左旋:【降低右子树的高度,增加左子树的高度】

思想:

1.创建一个新的节点newNode,值等与当前结点的值。

2.把新节点的左子树设置为当前结点的左子树

newNode.left = left;

3.把新节点的右子树设置成当前结点的右子结点的左子树

newNode.right = right.left;

4.把当前结点的值更换成为当前结点的右子结点的值

value = right.value;

5.把当前节点的左子树设置成以新节点为根结点的树

left = newNode;

图解:

代码实现:

/**
     * 使用左旋转的时机: 添加完一个节点后,就判断:树的右子树的高度 - 左子树的高度是否大于1:
     *                  如果大于1,则进行左旋转
     *   左旋转的方法:
     */
    public void leftRotate() {

        //1.创建一个新的节点,值等于当前结点的值
        Node newNode = new Node(this.value);

        //2.把新节点的左子树设置成为当前结点的左子树
        newNode.left = this.left;

        //3.把新节点的右子树设置成为当前结点的右子结点的左子树
        newNode.right = this.right.left;

        //4.将当前结点的值设置成为当前结点的右子结点的值
        this.value = this.right.value;

        //5.将当前结点的右子树设置为当前结点的右子树的右子树
        this.right = this.right.right;

        //6.将当前结点的左子结点设置为以新节点为根结点的树
        this.left = newNode;

    }

右旋

给定数列{10,12,8,9,7,6},创建一个平衡二叉树?

在添加节点过程中会出现:

         添加完某一节点后,发现此时右子树的高度-左子树的高度 > 1,此时如何解决?

解决方法:进行右旋【降低左子树的高度,增加右子树的高度】

思想:

1.创建一个新节点newNode,值等于当前结点的值;

2.把新节点的右子树设置成当前结点的右子树;

newNode.right = right;

3.把新节点的左子树设置成当前结点的左子结点的右子树;

newNode.left = left.right;

4.把当前结点的值更改成当前结点的左子结点的值;

value = left.value;

5.把当前结点的左子树设置成当前结点的左子结点的左子树;

left = left.left;

6.把当前结点的右子树设置成以新节点newNode为根结点的树。

right = newNode;

图解:

代码实现:

    /**
     * 使用右旋转的时机: 添加完一个节点后,就判断:树的左子树的高度 - 右子树的高度是否大于1:
     *                  如果大于1,则进行右旋转
     *   右旋转的方法:
     */
    public void rightRotate() {

        //1.创建一个新节点newNode,值等于当前结点的值
        Node newNode = new Node(this.value);

        //2.将新节点的右子树设置成当前结点的右子树
        newNode.right = this.right;

        //3.将新节点的左子树设置成当前结点的左子结点的右子树
        newNode.left = this.left.right;

        //4.将当前结点的值更改为当前节点的左子结点的值
        this.value = this.left.value;

        //5.将当前结点的左子树设置成当前节点的左子结点的左子树
        this.left = this.left.left;

        //6.将当前结点的右子树设置成以新节点newNode为根节点的树
        this.right = newNode;

    }

添加节点:

在添加某一节点后如何判断判断出需要进行左旋或者右旋?

 思路:

   ① 添加完某一节点后,如果发现此时左子树的高度 - 右子树的高度 > 1,那么需要对以当前结点为根节点的树调用右旋,但是在调用右旋之前应当考虑:

           如果当前节点的左子结点的右子树的高度 >当前节点的左子结点的左子树的高度,那么我们此时需要对以当前节点的左子结点为根结点的树进行左旋。(如果不这样做,就会导致即使对以当前结点为根结点的树进行右旋,但调整后的树也不是一个AVL树)

   ② 添加完某一节点后,如果发现此时右子树的高度 - 左子树的高度 > 1,那么需要对以当前结点为根结点的树调用左旋,但是在调用左旋之前应当考虑:

           如果当前节点的右子结点的左子树的高度 >当前节点的右子结点的右子树的高度,那么我们此时需要对以当前节点的右子结点为根结点的树进行右旋。(如果不这样做,就会导致即使对以当前结点为根结点的树进行左旋,但调整后的树也不是一个AVL树) 

图解:

代码实现:

 //当添加完一个节点后,判断树的左右子树的高度差的绝对值是否超过1:
         //如果右子树的高度 - 左子树的高度 > 1,则进行左旋转:
        if(this.rightHeight() - this.leftHeight() > 1) {
             //如果当前结点的右子结点的左子树的高度 > 当前结点的右子结点的右子树的高度,则对当前节点的右子结点进行右旋转
            if(this.right != null && this.right.leftHeight() > this.right.rightHeight()) {
                this.right.rightRotate();
            }
            this.leftRotate();
            return; //第一种情况已经解决,不用再考虑另外一种情况
        }


         //如果左子树的高度 - 右子树的高度 > 1,则进行右旋转:
        if(this.leftHeight() - this.rightHeight() > 1) {
            //如果当前结点的左子结点的右子树的高度 > 当前结点的左子结点的左子树的高度,则对当前结点的左子结点进行左旋转
            if(this.left != null && this.left.rightHeight() > this.left.leftHeight()) {
                this.left.leftRotate();
            }
            this.rightRotate();
        }

AVL树的创建(节点的添加)整体代码:

 /**
     *  添加插入节点
     * @param value
     */
    public void add(int value) {
        //1.添加节点
        if(value < this.value) {
            if(this.left == null) {
                this.left = new Node(value);
            } else {
                this.left.add(value);
            }
        } else {
            if(this.right == null) {
                this.right = new Node(value);
            } else {
                this.right.add(value);
            }
        }

        //2.当添加完一个节点后,判断树的左右子树的高度差的绝对值是否超过1:
          //如果右子树的高度 - 左子树的高度 > 1,则进行左旋转:
        if(this.rightHeight() - this.leftHeight() > 1) {
             //如果当前结点的右子结点的左子树的高度 > 当前结点的右子结点的右子树的高度,则对当前节点的右子结点进行右旋转
            if(this.right != null && this.right.leftHeight() > this.right.rightHeight()) {
                this.right.rightRotate();
            }
            this.leftRotate();
            return; //第一种情况已经解决,不用再考虑另外一种情况
        }


          //如果左子树的高度 - 右子树的高度 > 1,则进行右旋转:
        if(this.leftHeight() - this.rightHeight() > 1) {
            //如果当前结点的左子结点的右子树的高度 > 当前结点的左子结点的左子树的高度,则对当前结点的左子结点进行左旋转
            if(this.left != null && this.left.rightHeight() > this.left.leftHeight()) {
                this.left.leftRotate();
            }
            this.rightRotate();
        }

    }

平衡二叉树节点的删除、查询、遍历

与二叉排序树节点的删除、查询、遍历思想代码相同。具体点击二叉排序树_小何小何h的博客-CSDN博客

平衡二叉树完整汇总代码

import org.junit.Test;

/**
 * AVL树(平衡二叉树):
 *
 */
public class AVLTreeDemo {

    @Test
    public void test1() {
        //左旋测试:
        int arr1[] = {4,3,6,5,7,8};
        System.out.println("左旋:");
        AVLTree avlTree1 = new AVLTree();
        for(int value : arr1) {
            avlTree1.add(value);
        }
        avlTree1.preOrder();//正确为:6 4 3 5 7 8
    }

    @Test
    public void test2() {
        //右旋测试:
        int arr2[] = {10,12,8,9,7,6};
        System.out.println("右旋:");
        AVLTree avlTree2 = new AVLTree();
        for(int value : arr2) {
            avlTree2.add(value);
        }
        avlTree2.preOrder(); //正确为:8 7 6 10 9 12
    }

    @Test
    public void test3() {
        //双旋测试1:
        int arr3[] = {10,11,7,6,8,9};
        System.out.println("双旋1:");
        AVLTree avlTree3 = new AVLTree();
        for(int value : arr3) {
            avlTree3.add(value);
        }
        avlTree3.preOrder();//正确为:8 7 6 10 9 11
    }

    @Test
    public void test4() {
        //双旋测试2:
        int arr4[] = {2,1,6,5,7,3};
        System.out.println("双旋2:");
        AVLTree avlTree4 = new AVLTree();
        for(int value : arr4) {
            avlTree4.add(value);
        }
        avlTree4.preOrder();//正确为:5 2 1 3 6 7
    }

}

//创建AVL树
class AVLTree {
    private Node root;

    public AVLTree() {

    }

    public AVLTree(Node root) {
        this.root = root;
    }

    public Node getRoot() {
        return root;
    }

    /**
     * 平衡二叉树的创建(结点的添加):
     */
    public void add(int value) {
        if(root == null) {
            root = new Node(value);
        }else{
            root.add(value);
        }
    }


    /**
     * 平衡二叉树结点的删除:三种情况:
     *   1) 如果要删除的节点是一个叶子节点,则:直接删除该结点;
     *   2) 如果要删除的节点只有一个子结点,则:替换要删除的节点为其子结点;
     *   3) 如果要删除的节点有两个子结点,则:先找到以要删除的节点的为根节点的右子树中最小的结点,将要删除的结点的值替换成最小结点的值,删除最小结点。
     *      (或者首先找到以要删除的节点的为根节点的左子树中最大的结点,后面的操作相同)
     *
     */

    public void deleteNode(int value) {
        if(root == null) {
            System.out.println("当前树为空,无法删除指定值的节点");
            return;
        } else {
            //找要删除的结点;
            Node targetNode = search(value);

            if(targetNode == null) {
                System.out.printf("该树中没有值为%d的节点,无法删除", value);
                System.out.println();
                return;
            }

            //如果只有一个根节点,且就是要删除的节点,就让树为空即可;否则就结束删除
            if (root.left == null && root.right == null) {
                if (value == root.value) {
                    root = null;
                    return;
                }
            }

            //找要删除节点的父结点:(另外写一个方法,直接调用)
            Node parent = searchParent(value);

            //第一种情况:
            /*如果删除的是叶子结点,则直接删除。步骤:
                1、找到要删除的结点
                2、找到要删除的结点的父结点
                3、判断要删除的节点是父结点的什么节点?如果是左子结点:parent.left = null;反之,则:parent.right = null;
            */
            //第一种情况
            if(targetNode.left == null && targetNode.right == null) {
                if(parent.left != null && parent.left.value == targetNode.value) {
                    parent.left = null;
                }
                if(parent.right != null && parent.right.value == targetNode.value) {
                    parent.right = null;
                }
                return;
            }

            /*如果删除的是只有一个子节点的节点,则替换要删除的节点为其子结点。步骤:
                1.查找要删除的结点
                2.查找要删除的结点的父结点
                3.判断要删除的节点是父结点的什么节点?
                4.判断要删除的结点的唯一子结点是它的左子结点还是右子结点?

             */
            //第二种情况
            if((targetNode.left != null && targetNode.right == null ) ||(targetNode.left == null && targetNode.right != null)){
                if(parent.left.value == targetNode.value) {
                    if(targetNode.left != null) {
                        parent.left = targetNode.left;
                    } else {
                        parent.left = targetNode.right;
                    }
                } else {
                    if(targetNode.left != null) {
                        parent.right = targetNode.left;
                    } else {
                        parent.right = targetNode.right;
                    }
                }
            } else {
                /*第三种情况:
                  如果删除的是有两个子结点的结点,则在以要删除的结点为根结点的右子树的最小结点,再将其值保存在临时变量temp中,
                将要删除的结点的值替换成temp,将最小结点删除。
                 */
               targetNode.value = RightMixNode(new Node(value).right);
            }

        }

    }

    //查找以删除的结点为根节点的右子树中的最小结点

    public int RightMixNode(Node node) {
        int temp = 0;
        while (node.left != null) {
            node = node.left;
            temp = node.value;
        }
        deleteNode(node.value);
        return temp;
    }



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



    /**
     * 平衡二叉树的查找:
     */
    public Node search(int value) {
        if(root == null) {
            return null;
        } else {
            return root.search(value);
        }
    }


    /**
     * 平衡二叉树的遍历:
     */
    //前序遍历
    public void preOrder() {
        if(root == null) {
            System.out.println("树为空,无法完成前序遍历");
        } else {
            root.preOrder();
        }
    }

    //中序遍历
    public void infixOrder() {
        if(root == null) {
            System.out.println("树为空,无法完成中序遍历");
        } else {
            root.infixOrder();
        }
    }

    //后序遍历
    public void postOrder() {
        if(root == null) {
            System.out.println("树为空,无法完成后序遍历");
        } else {
            root.postOrder();
        }
    }

}

//创建一个节点类
class Node {
    int value;
    Node left; //左指针
    Node right;//右指针

    public Node() {

    }

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

    @Override
    public String toString() {
        return "Node{" +
                "value=" + value +
                '}';
    }

    /**
     *  添加插入节点
     * @param value
     */
    public void add(int value) {
        //1.添加节点
        if(value < this.value) {
            if(this.left == null) {
                this.left = new Node(value);
            } else {
                this.left.add(value);
            }
        } else {
            if(this.right == null) {
                this.right = new Node(value);
            } else {
                this.right.add(value);
            }
        }

        //2.当添加完一个节点后,判断树的左右子树的高度差的绝对值是否超过1:
          //如果右子树的高度 - 左子树的高度 > 1,则进行左旋转:
        if(this.rightHeight() - this.leftHeight() > 1) {
             //如果当前结点的右子结点的左子树的高度 > 当前结点的右子结点的右子树的高度,则对当前节点的右子结点进行右旋转
            if(this.right != null && this.right.leftHeight() > this.right.rightHeight()) {
                this.right.rightRotate();
            }
            this.leftRotate();
            return; //第一种情况已经解决,不用再考虑另外一种情况
        }


          //如果左子树的高度 - 右子树的高度 > 1,则进行右旋转:
        if(this.leftHeight() - this.rightHeight() > 1) {
            //如果当前结点的左子结点的右子树的高度 > 当前结点的左子结点的左子树的高度,则对当前结点的左子结点进行左旋转
            if(this.left != null && this.left.rightHeight() > this.left.leftHeight()) {
                this.left.leftRotate();
            }
            this.rightRotate();
        }

    }

    /**
     * 删除节点
     *
     */
    //查找要删除节点的父结点
    public Node searchParent(int value) {
        if((this.left != null && this.left.value == value) || (this.right != null && this.right.value == value)) {
            return this;
        } else if(value < this.left.value ) {
            if(this.left == null) {
                return null;
            }
            return this.left.search(value);
        } else {
            if(this.right == null) {
                return null;
            }
            return this.right.search(value);
        }
    }



    /**
     * 查找节点:
     */
    public Node search(int value) {
        if(value == this.value) {
            return this;
        } else if(value < this.value) {
            if(this.left != null) {
                return this.left.search(value);
            } else {
                return null;
            }
        } else {
            if(this.right != null) {
                return this.right.search(value);
            } else {
                return null;
            }
        }
    }

    /**
     * 遍历:
     */
      //前序遍历
    public void preOrder(){
        System.out.println(this);
        if(this.left != null) {
            this.left.preOrder();
        }
        if(this.right != null) {
            this.right.preOrder();
        }
    }
    //中序遍历
    public void infixOrder() {
        if(this.left != null) {
            this.left.infixOrder();
        }
        System.out.println(this);
        if(this.right != null) {
            this.right.infixOrder();
        }
    }
    //后序遍历
    public void postOrder() {
        if (this.left != null) {
            this.left.postOrder();
        }
        if (this.right != null) {
            this.right.postOrder();
        }
        System.out.println(this);
    }




    //返回当前结点的左子树的高度
    public int leftHeight() {
        if(left != null) {
            return left.height();
        } else {
            return 0;
        }
    }

    //返回当前结点的右子树的高度
    public int rightHeight() {
        if(right != null) {
            return right.height();
        } else {
            return 0;
        }
    }

    //返回以该结点为根结点的树的高度
    public int height() {
        return Math.max(left == null ? 0 : left.height(),right == null ? 0 : right.height()) + 1;
    }

    /**
     * 使用左旋转的时机: 添加完一个节点后,就判断:树的右子树的高度 - 左子树的高度是否大于1:
     *                  如果大于1,则进行左旋转
     *   左旋转的方法:
     */
    public void leftRotate() {

        //1.创建一个新的节点,值等于当前结点的值
        Node newNode = new Node(this.value);

        //2.把新节点的左子树设置成为当前结点的左子树
        newNode.left = this.left;

        //3.把新节点的右子树设置成为当前结点的右子结点的左子树
        newNode.right = this.right.left;

        //4.将当前结点的值设置成为当前结点的右子结点的值
        this.value = this.right.value;

        //5.将当前结点的右子树设置为当前结点的右子树的右子树
        this.right = this.right.right;

        //6.将当前结点的左子结点设置为以新节点为根结点的树
        this.left = newNode;

    }

    /**
     * 使用右旋转的时机: 添加完一个节点后,就判断:树的左子树的高度 - 右子树的高度是否大于1:
     *                  如果大于1,则进行右旋转
     *   右旋转的方法:
     */
    public void rightRotate() {

        //1.创建一个新节点newNode,值等于当前结点的值
        Node newNode = new Node(this.value);

        //2.将新节点的右子树设置成当前结点的右子树
        newNode.right = this.right;

        //3.将新节点的左子树设置成当前结点的左子结点的右子树
        newNode.left = this.left.right;

        //4.将当前结点的值更改为当前节点的左子结点的值
        this.value = this.left.value;

        //5.将当前结点的左子树设置成当前节点的左子结点的左子树
        this.left = this.left.left;

        //6.将当前结点的右子树设置成以新节点newNode为根节点的树
        this.right = newNode;

    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值