数据结构 -- AVL平衡二叉树Java代码实现

平衡二叉树

特点

  • 首先它满足二叉查找树的所有特点
  • 任何节点的两个子树的高度最大差为1
    新增和删除节点可能导致avl树失去平衡,可以通过旋转重新达到平衡

缺点

  • 要求严格,几乎每一次操作都会破坏平衡,需要进行旋转调整。

右旋、左旋、双旋(以右旋为例、左旋同理)

当左子树的高度高于右子树的高度时,我们需要进行右旋操作
当右子树的高度高于左子树的高度时,我们需要进行左旋操作

右旋为例

分两种情况:
一、插入的节点是左子节点

  • 此时将目标节点向右旋转即可,即左子节点【8】提升为父节点,目标节点【10】成为原来左子节点【8】的右子节点
    在这里插入图片描述

二、插入的节点是右子节点

  • 此时我们直接右旋转会发现仍不平衡
    在这里插入图片描述

  • 我们可以先将节点【8】左旋,在将节点【10】右旋

  • 在这里插入图片描述

  • 第一步、节点【9】替换节点【8】的位置,节点【8】成为【9】的左子节点

  • 第二步、与普通右旋相同

JAVA代码实现

代码大部分内容与二叉查找树相同,AVL树本质上就是BST树,这里添加了一些处理平衡的方法

  • 获取节点左子节点和右子节点的深度
  • 判断节点是否平衡 (深度差<2)
  • 按照节点左高还是右高进行旋转
  • 注意根节点的处理(旋转之后成为根节点的节点要清除原来的父节点信息)
节点类
public class Node {
    
    

    private Integer value;
    private Node parentNode;
    private Node leftNode;
    private Node rightNode;

    public Node(){
    
    
    }

    /**
     * 当节点上升为根节点时,提供可以清除父节点的方法
     */
    public void clearParent(){
    
    
        this.parentNode =null;
    }

    /**
     * 自动设置父节点
     * @param node
     */
    private void setParentNode(Node node){
    
    
        this.parentNode = node;
    }

    public Node getParentNode(){
    
    
       return this.parentNode;
    }


    public Integer getValue(){
    
    
        return this.value;
    }

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

    public Node getLeftNode(){
    
    
        return this.leftNode;
    }

    public void setLeftNode(Node node){
    
    
        if(null != node ) node.setParentNode(this);
        this.leftNode = node;
    }

    public Node getRightNode(){
    
    
        return this.rightNode;
    }

    public void setRightNode(Node node){
    
    
        if(null != node ) node.setParentNode(this);
        this.rightNode = node;
    }

    /**
     * 前序遍历
     */
    public void DLR(){
    
    
        if(value!=null) {
    
    
            System.out.print(String.format("【%d】", value));
            if (this.leftNode != null) {
    
    
                this.leftNode.DLR();
            }
            if (this.rightNode != null) {
    
    
                this.rightNode.DLR();
            }
        }
    }

    /**
     * 中序遍历
     */
    public void LDR(){
    
    
        if(value!=null) {
    
    
            if (this.leftNode != null) {
    
    
                this.leftNode.LDR();
            }
            System.out.print(String.format("【%d】", value));
            if (this.rightNode != null) {
    
    
                this.rightNode.LDR();
            }
        }
    }

    /**
     * 后续遍历
     */
    public void LRD() {
    
    
        if (value != null) {
    
    
            if (this.leftNode != null) {
    
    
                this.leftNode.LRD();
            }
            if (this.rightNode != null) {
    
    
                this.rightNode.LRD();
            }
            System.out.print(String.format("【%d】", value));
        }
    }
}

平衡二叉树操作类
public class AvlTree {
    
    

    private Node root;

    public Node getTree(){
    
    
        return this.root;
    }

    /**
     * 插入元素
     * @param i
     */
    public void insert(int i){
    
    

        Node newNode = new Node();
        newNode.setValue(i);

        if(root==null){
    
    
            //没有根结点时 将当前节点作为根节点
            root=newNode;
        }else{
    
    
            Node currentNode = root;
            Node parentNode;

            while (null != currentNode) {
    
    
                // 当前节点视为父节点
                parentNode = currentNode;
                if (i > currentNode.getValue()) {
    
    
                    // 大于父节点的值 且父节点没有右子节点 则作为右节点插入
                    currentNode = currentNode.getRightNode();
                    if (null == currentNode) {
    
    
                        parentNode.setRightNode(newNode);
                    }
                } else if(i < currentNode.getValue()) {
    
    
                    // 小于父节点的值 且父节点没有左子节点 则作为左节点插入
                    currentNode = currentNode.getLeftNode();
                    if (null == currentNode) {
    
    
                        parentNode.setLeftNode(newNode);
                    }
                }
            }
            //插入之后 平衡树结构
            if (newNode != null) {
    
    
                balance(newNode);
            }
        }
    }

    /**
     * 因为原来的树是平衡的 所以我们从插入的节点向上判断
     * @param node
     */
    private void balance(Node node) {
    
    
        Node target = node;

        //从目标节点向上遍历左右节点的最大树高差
        while (target != null) {
    
    
            Node l = target.getLeftNode();
            Node r = target.getRightNode();
            int deep = getDeep(l) - getDeep(r);

            //左高于右 右旋
            if (deep == 2) {
    
    
                rightRotate(node, target);
                break;
            }

            //右高于左 左旋
            if (deep == -2) {
    
    
                leftRotate(node, target);
                break;
            }

            target = target.getParentNode();
        }
    }

    /**
     * 获取节点的深度
     * @param node
     * @return
     */
    private int getDeep(Node node) {
    
    
        if (node == null) {
    
    
            return 0;
        }

        //自身节点 高为1
        if (node.getLeftNode() == null && node.getRightNode() == null) {
    
    
            return 1;
        }

        return 1 + Math.max(getDeep(node.getLeftNode()), getDeep(node.getRightNode()));
    }

    /**
     * 左树高于右树 右旋
     * 插入的节点是 右子节点 需要先架构插入节点的父节点左旋
     * @param node
     * @param target
     */
    private void rightRotate(Node node, Node target) {
    
    
        if (node == node.getParentNode().getRightNode()) {
    
    
            leftRound(node.getParentNode());
        }

        rightRound(target);
    }

    /**
     * 右旋操作
     * @param node
     */
    private void rightRound(Node node) {
    
    

        // 目标节点的左子节点
        Node left = node.getLeftNode();
        // 目标节点的父节点
        Node pre = node.getParentNode();

        if (pre == null) {
    
    
            // 若父节点为空,即目标节点原本是根节点,则目标节点左孩子晋升为根节点
            //清除根节点原来的父节点信息
            left.clearParent();
            root = left;
        } else {
    
    
            //left 作为 pre 的子节点 即left替换原来node的位置
            updateNode(node,left);
        }

        //left的右子节点 作为 node的左子节点
        node.setLeftNode(left.getRightNode());

        //node 作为 left的右子节点
        left.setRightNode(node);

    }

    /**
     * 右树高于左树 左旋
     * 插入的节点是 左子节点 需要先架构插入节点的父节点右旋
     * @param node
     * @param target
     */
    private void leftRotate(Node node, Node target) {
    
    
        if (node == node.getParentNode().getLeftNode()) {
    
    
            rightRound(node.getParentNode());
        }

        leftRound(target);
    }

    /**
     * 左旋操作
     * @param node
     */
    private void leftRound(Node node) {
    
    

        // 目标节点的右子节点
        Node right = node.getRightNode();
        // 目标节点的父节点
        Node pre = node.getParentNode();

        if (pre == null) {
    
    
            // 若父节点为空,即目标节点原本是根节点,则目标节点左孩子晋升为根节点
            //清除根节点原来的父节点信息
            right.clearParent();
            root = right;
        } else {
    
    
            //right 作为 pre 的子节点 即right替换原来node的位置
            updateNode(node,right);
        }
        //right的左子节点 作为 node的右子节点
        node.setRightNode(right.getLeftNode());

        //node 作为 right的左子节点
        right.setLeftNode(node);

    }

    /**
     * 查找元素节点
     * @param key
     * @return
     */
    public Node find(int key) {
    
    
        Node currentNode = root;
        if (null != currentNode) {
    
    
            // 判断是否是当前节点
            while (key != currentNode.getValue()) {
    
    
                //值大于当前节点 向右遍历
                if (key > currentNode.getValue()) {
    
    
                    currentNode = currentNode.getRightNode();
                } else {
    
    
                    //值小于当前节点 向左遍历
                    currentNode = currentNode.getLeftNode();
                }
                if (null == currentNode) {
    
    
                    return null;
                }
            }
        }
        return currentNode;
    }



    /**
     * 查找 node 的后继节点
     * @param node
     * @return
     */
    public Node successor(Node node) {
    
    
        //后继节点 即 右节点的最小值
        Node minNode = null;
        if(node.getRightNode()!=null){
    
    
            minNode=node.getRightNode();
            while(minNode!=null){
    
    
                Node currentChild = minNode.getLeftNode();
                if(currentChild!=null){
    
    
                    minNode = minNode.getLeftNode();
                }else{
    
    
                    break;
                }
            }
        }
        return minNode;
    }

    /**
     * 用新的节点替换当前节点
     * @param oldNode
     * @param newNode
     */
    private void updateNode(Node oldNode,Node newNode){
    
    
        if (oldNode.getParentNode().getLeftNode() == oldNode){
    
    
            oldNode.getParentNode().setLeftNode(newNode);
        }else if (oldNode.getParentNode().getRightNode() == oldNode){
    
    
            oldNode.getParentNode().setRightNode(newNode);
        }
    }

    /**
     * 删除节点 分三种情况
     * 叶子节点 直接删除
     * 有一个子节点  将子节点的父节点设置为被删除节点的父节点
     * 左右节点都存在时 可以先找到需要删除的节点的后继节点或前驱节点替换当前节点
     * @param key
     */
    public void delete(int key) {
    
    

        Node deleteNode = find(key);

        if(deleteNode!=null){
    
    
            Node parent = deleteNode.getParentNode();
            if(deleteNode.getLeftNode()!=null && deleteNode.getRightNode()!=null){
    
    

                //两个子节点,查找后继节点替换当前节点
                Node backContinueNode = successor(deleteNode);

                //将后继节点 替换为 NULL
                this.updateNode(backContinueNode,null);

                //将待删除节点的子节点内容赋值给 后继节点
                backContinueNode.setLeftNode(deleteNode.getLeftNode());
                //后继节点有右子节点
                Node rightNode = backContinueNode.getRightNode();
                if(rightNode!=null){
    
    

                    //获得最后一个右子节点 赋值
                    while(rightNode!=null){
    
    
                        if(rightNode.getRightNode()!=null){
    
    
                            rightNode=rightNode.getRightNode();
                        }else{
    
    
                            //最后一个右子节点
                            rightNode.setRightNode(deleteNode.getRightNode());
                            break;
                        }
                    }
                }else{
    
    
                    backContinueNode.setRightNode(deleteNode.getRightNode());
                }
                //将待删除节点 替换为 后继节点
                //需要删除的节点没有被引用的地方,会自动回收
                this.updateNode(deleteNode,backContinueNode);
            }else if(deleteNode.getLeftNode()!=null){
    
    

                //只有左子节点 将左子节点和父节点直接相连
                this.updateNode(deleteNode,deleteNode.getLeftNode());
            }else if(deleteNode.getRightNode()!=null){
    
    

                //只有右子节点 将右子节点和父节点直接相连
                this.updateNode(deleteNode,deleteNode.getRightNode());
            }else{
    
    

                //没有子节点
                this.updateNode(deleteNode,null);
            }

            //平衡操作
            if(parent!=null){
    
    
                balance(parent);
            }else{
    
    
                balance(root);
            }
        }else{
    
    
            throw new RuntimeException("没有该节点");
        }
    }
}

Guess you like

Origin blog.csdn.net/qq_40096897/article/details/121498962