AVL自平衡二叉树

自平衡二叉树的难点在于左旋和右旋,详细步骤如下:

/**
 * 自平衡二叉树左旋:
 *     1、以当前根节点的值创建一个新的节点 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
 *
 *      做完左旋后,剩余之前根节点指向的右子树,这个树现在没人指向它,在java中会被垃圾回收。
 *
 * 自平衡二叉树右旋是完全类似的:
 *        1、以当前根节点的值创建一个新的节点
 *        2、把新节点的右子树设置为当前节点的右子树
 *        3、新节点的左子树设置为当前节点左子树的右子树 n
 *        4、当前节点的值替换为左子节点的值
 *        5、当前节点的左子树设置为左子树的左子树
 *        6、当前节点的右子树设置为前几步新建的那个节点
 *
 *
 * 还有一种情况,可能会导致一棵树左旋后还不满足自平衡二叉树(右选类似),这时候想要做的是:双旋转
 *      在符合右旋转条件时:
 *      1、如果左子树的右子树高度大于它的左子树的高度
 *      2、先对当前这个节点的左节点进行左旋(变成了一长条,这时候已经比较合适做右旋转)
 *      3、再对当前节点进行右旋
 *
 */

从节点类创建、平衡树创建到测试的带注解详细代码:

package cn.edu.uestc.datastrure.Tree;

import java.util.Map;
public class AVLTreeDemo {
    public static void main(String[] args) {
        int[] arr = {10,11,7,6,8,9};
        AVLTree avlTree = new AVLTree();
        //添加节点
        for (int i = 0; i < arr.length; i++) {
            avlTree.add(new Node04(arr[i]));
        }
        avlTree.infixOrder();
        System.out.println("-----------------");

        //avlTree.delNode(7);
        //avlTree.infixOrder();

        //由于寻找子树高度的方法只写在Node类中,没写到avl树上,所以想获取高度,就需要用具体节点去调用
        //比如可以操作avlTree的root节点(下面是没加旋转代码时的结果)
        System.out.println(avlTree.root.height());//3
        System.out.println(avlTree.root.leftHeight());//2
        System.out.println(avlTree.root.rightHeight());//2

    }
}

class AVLTree{
    Node04 root;

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

    public AVLTree() {
    }

    /**
     * 为树添加节点
     */
    public void add(Node04 node){
        if (root == null){
            root = node;
        }else {
            root.add(node);
        }
    }

    /**
     * infix遍历
     */
    public void infixOrder(){
        if (root == null){
            System.out.println("当前树为空");
        }else {
            root.infixOrder();
        }
    }
    /**
     * 查找目标节点
     * @param value
     * @return
     */
    public Node04 searchTargetNode(int value){
        if (this.root == null){
            System.out.println("当前节点为空");
            return null;
        }else {
            return  root.searchTargetNode(value);
        }
    }

    /**
     * 查找目标节点的父节点
     */
    public Node04 searchParent(int value){
        if (this.root == null){
            System.out.println("当前二叉树为空");
            return null;
        }else {
            return this.root.searchParent(value);
        }
    }

    /**
     *  找到targetNode右子树最小值,值放到targetNode中,并删除这个节点
     * @param node 传进来的是targetNode.left
     * @return 返回targetNode右子树最小值,之后用于把这个值赋给targetNode
     */
    public int delRightTreeMin(Node04 node){
        if (node != null) {//其实这个可以不用判断,因为输入的时候在外层调用就判断过了
            while (node.getLeft() != null) {
                node = node.getLeft();
            }
        }
        //执行到这里node03.getLeft() == null,说明以及找到右子树的最小值(向左就是小的)
        int temp = node.getValue();
        delNode(node.getValue());
        return temp;
    }

    /**
     * 删除节点的方法
     * @param value
     */
    public void delNode(int value){
        if (root == null){
            return;
        }else {
            //先获取当前节点
            Node04 targetNode = root.searchTargetNode(value);
            if (targetNode == null){
                System.out.println("未找到对应value值的节点");
                return;
            }
            //如果我们发现当前这颗二叉排序树只有一个结点
            if (root.getRight() == null && root.getLeft() == null){
                root = null;
                return;
            }
            //去寻找当前节点的父节点
            Node04 parent = this.searchParent(value);

            //开始分情况讨论; 1、要删除的节点是叶子节点
            if (targetNode.getLeft() == null && targetNode.getRight() == null){
                System.out.println("要删除的节点是叶子节点");

                if (parent.getLeft().getValue() == value){
                    parent.setLeft(null);
                }else {
                    parent.setRight(null);
                }
            } else if (targetNode.getRight() != null && targetNode.getLeft() != null) {
                //2、目标节点有两颗子树
                int min = this.delRightTreeMin(targetNode.getRight());
                targetNode.setValue(min);
            }else {
                //3、目标节点只有一颗子树

                if (targetNode.getLeft() != null){ //目标节点有左节点
                    if (parent != null){//等于空直接挂在root上
                        if (parent.getLeft() != null && parent.getLeft().getValue() == value){
                            //目标节点是parent的左节点

                            //不能下面这样写!这样只是把值该了,我们应该该变指向,之后删除节点没人指定后,被垃圾回收!
                            //parent.getLeft().setValue(targetNode.getLeft().getValue());
                            parent.setLeft(targetNode.getLeft());
                        }
                    }else {
                        root.setLeft(targetNode.getLeft());
                    }
                }else {//目标节点只有右节点
                    if (parent != null){
                        if (parent.getRight() != null && parent.getRight().getValue() == value){
                            parent.setRight(targetNode.getRight());
                        }
                    }else {
                        parent.setLeft(targetNode.getRight());
                    }
                }
            }
        }
    }
}

class Node04{

    private int value;
    private Node04 left;
    private Node04 right;

    /**
     * 添加节点的方法(由于自平衡二叉树可以由二叉排序树左旋、右旋而来。所以添加、删除等方法可以先按照二叉排序树写,先构成一个二叉排序树)
     * 所以只要是二叉排序树的添加删除,都要注意 左<中<右 的规则
     * @param node
     */
    public void add(Node04 node){
        if(node == null){
            System.out.println("当前节点为空,无法添加");
            return;
        }
        if (node.value < this.value){
            if (this.left == null){
                this.left = node;

            }else {
                this.left.add(node);
            }
        }
        if (node.value > this.value){
            if (this.right == null){
                this.right = node;

            }else {
                this.right.add(node);
            }
        }

        //当添加完一个结点后,如果: (右子树的高度-左子树的高度) > 1 , 左旋转
        if ((rightHeight() - leftHeight()) > 1){
            //如果它的右子树的左子树的高度大于它的右子树的右子树的高度
            if (right!=null && this.right.leftHeight() > this.right.rightHeight()){
                //先对右子结点进行右旋转
                this.right.rightRotate();
                //然后在对当前结点进行左旋转
                this.leftRotate();
            }else {
                //直接左旋
                leftRotate();
            }
            return;//千万不能丢,如果满足上面条件执行完毕后就完成调整了,不能再向下调整。
            //如果上面的return不写,下面应该用else if(....)
        }

        //当添加完一个结点后,如果 (左子树的高度 - 右子树的高度) > 1, 右旋转
        if(leftHeight() - rightHeight() > 1) {
            //如果它的左子树的右子树高度大于它的左子树的高度
            if(left != null && left.rightHeight() > left.leftHeight()) {
                //先对当前结点的左结点(左子树)->左旋转
                left.leftRotate();
                //再对当前结点进行右旋转
                rightRotate();
            } else {
                //直接进行右旋转即可
                rightRotate();
            }
        }
    }



    /**
     * 搜索当前节点(还没测试)
     * @param value
     * @return
     */
    public Node04 searchTargetNode(int value){
        if (this.value == value){
            return this;
        }else {
            if (this.left != null && value < this.value){
                return this.left.searchTargetNode(value);
            }else if (this.right != null && value > this.value){
                return this.right.searchTargetNode(value);
            }else {
                System.out.println("未找到目标节点");
                return null;
            }
        }
    }

    /**
     * 搜索当前节点的父节点
     * @return
     */
    public Node04 searchParent(int value){
        if ((this.left != null && this.left.value == value) || (this.right != null && this.right.value == value)){
            return this;
        }else {
            if (this.left != null && value < this.value){
                return this.left.searchParent(value);
            }else if (this.right != null && value < this.value){
                return this.right.searchParent(value);
            }else {
                return null;
            }
        }
    }

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

    //avl自平衡二叉树中,左右子树的高度是很重要的参数

    /**
     * 获取子树高度
     * @return
     */
    public int height(){
        //很巧妙的代码,递归到最后了,第一次+1,之后栈结构退出来,每一轮都+1,知道第一次调用,正好计算好了树的高度
        return Math.max(left == null ? 0 : left.height(),right == null ? 0 : right.height()) +1;
    }

    /**
     * 返回左子树的高度
     * @return
     */
    public int leftHeight(){
        if (this.left == null){
            return 0;
        }else {
            return left.height();
        }
    }

    /**
     * 返回右子树的高度
     * @param
     */
    public int rightHeight(){
        if (this.right == null){
            return 0;
        }else {
            return right.height();
        }
    }

    /**
     * 左旋
     */
    private void leftRotate(){
        // 1、以当前根节点的值创建一个新的节点 new Node(this.value)
        Node04 newNode = new Node04(this.value);
        // 2、把新节点的左子树设置为当前节点的左子树 newNode.left = this.left
        newNode.left = this.left;
        // 3、新节点的右子树设置为当前节点右子树的左子树 newNode.right = this.right.left
        newNode.right = this.right.left;
        // 4、当前节点的值替换为右子节点的值 this.value = this.right.value
        this.value = this.right.value;
        // 5、当前节点的右子树设置为右子树的右子树 this.right = this.right.right(本来右边很长,这样做后,就变短了)
        this.right = right.right;
        //6、当前节点的左子树设置为前几步新建的那个节点 this.left = newNode
        this.left = newNode;
    }

    /**
     * 右旋
     */
    private void rightRotate(){
        // 1、以当前根节点的值创建一个新的节点
        Node04 newNode = new Node04(this.value);
        // 2、把新节点的右子树设置为当前节点的右子树
        newNode.right = this.right;
        // 3、新节点的左子树设置为当前节点左子树的右子树
        newNode.left = this.left.right;
        // 4、当前节点的值替换为左子节点的值
        this.value = this.left.value;
        // 5、当前节点的左子树设置为左子树的左子树
        this.left = left.left;
        //6、当前节点的右子树设置为前几步新建的那个节点
        this.right = newNode;
    }

    public Node04(int value) {
        this.value = value;
    }///构造方法用于new对象,只要传入值就好,每个节点的左右关系在add()添加节点的方法里处理好了

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

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    public Node04 getLeft() {
        return left;
    }

    public void setLeft(Node04 left) {
        this.left = left;
    }

    public Node04 getRight() {
        return right;
    }

    public void setRight(Node04 right) {
        this.right = right;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值