14.平衡二叉树(AVL树)

左旋转思想:当右子树的高度比左子树的高度高时(并且高度差绝对值超过了1时)

代码示例:
    package cn.com.avlTree;

/**
 * 平衡二叉树
 */
public class AvlTreeDemo {
    public static void main(String[] args) {
        int arr[] = {4, 3, 6, 5,7, 8};
        AvlTree avlTree = new AvlTree();
        for (int i = 0; i < arr.length; i++) {
            avlTree.add(new Node(arr[i]));
        }
        System.out.println("中序遍历======");
        avlTree.infixOrder();
        System.out.println("\n节点4的高度:" + avlTree.height(4));
        System.out.println("节点6的高度:" + avlTree.height(6));
        System.out.println("节点3的高度:" + avlTree.height(3));
    }
}

class AvlTree {
    Node root;

    /**
     * 获取value节点为根节点的树的高度
     *
     * @param value 节点值
     * @return 以value节点为根节点的数的高度
     */
    public int height(int value) {
        //查找到以value的节点
        Node target = root.search(value);
        int height=0;
        if (target!=null){
            height=target.height();
        }
        return height;
    }

    /**
     * 添加元素
     *
     * @param node
     */
    public void add(Node node) {
        if (root == null) {
            root = node;
        } else {
            this.root.add(node);
        }
    }

    /**
     * 中序遍历
     */
    public void infixOrder() {
        if (root == null) {
            return;
        }
        this.root.infixOrder();

    }

    /**
     * 1.返回以目标节点为根节点的二叉排序树的最小节点的值
     * 2.删除目标节点为根节点的二叉排序树的最小节点
     *
     * @param node 目标节点的右子树节点
     * @return 右子树的最小值
     */
    public int delRightTreeMin(Node node) {
        Node target = node;
        //循环查找右子树中的左子树,找到的就是最小值
        while (target.getLeft() != null) {
            target = target.getLeft();
        }
        //删除最小节点
        /*
            思考点1:这里的代码为啥不直接写在删除代码中,而是抽取成一个方法呢
            如果写在删除deleteNode方法中,那方法中就是递归调用,没有像方法返回一个唯一的值
            回溯时可能会存在多次赋值的情况!
         */
        deleteNode(target.getValue());
        return target.getValue();
    }

    /**
     * 删除节点
     *
     * @param value 删除的节点值
     */
    public void deleteNode(int value) {
        if (root == null) {
            return;
        }
        //查找需要删除节点
        Node target = root.search(value);
        if (target == null) {
            //没找到不处理
            return;
        }
        //当只有一个节点并且就是需要删除的节点
        if (root.getLeft() == null && root.getRight() == null) {
            root = null;
        }
        //获取需要删除节点的父节点
        Node parent = root.searchParent(value);
        /*
         *第一种情况:当需要删除的节点是叶子节点时
         */
        if (target.getLeft() == null && target.getRight() == null) {
            if (parent.getLeft() != null && parent.getLeft().getValue() == target.getValue()) {
                //当需要删除的节点时父节点的左子节点时
                parent.setLeft(null);
            }
            if (parent.getRight() != null && parent.getRight().getValue() == target.getValue()) {
                parent.setRight(null);
            }
        } else if (target.getLeft() != null && target.getRight() != null) {
            //目标节点的两个子节点都不为空,在右子树上找最小的值
            int rightTreeMinValue = delRightTreeMin(target.getRight());
            target.setValue(rightTreeMinValue);
        } else {
            //目标节点的一个节点为空
            if (target.getLeft() != null) {
                if (parent != null) {
                    //删除目标节点的左子节点不为空
                    if (parent.getLeft() != null && parent.getLeft().getValue() == value) {
                        //目标节点在父节点的左边
                        parent.setLeft(target.getLeft());
                    } else {
                        //目标节点在父节点的右边
                        parent.setRight(target.getLeft());
                    }
                } else {
                    root = target.getLeft();
                }

            } else {
                if (parent != null) {
                    //需要删除的节点有右子节点
                    if (parent.getLeft() != null && parent.getLeft().getValue() == value) {
                        parent.setLeft(target.getRight());
                    } else {
                        parent.setRight(target.getRight());
                    }
                } else {
                    root = target.getRight();
                }
            }
        }
    }
}

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

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

    public int getValue() {
        return value;
    }

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

    public Node getLeft() {
        return left;
    }

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

    public Node getRight() {
        return right;
    }

    public void setRight(Node right) {
        this.right = right;
    }

    /**
     * 获取以当前节点为根节点的数的高度
     *
     * @return 以当前节点为根节点的数的高度
     */
    public int height() {
        return Math.max(left == null ? 0 : left.height(),
                right == null ? 0 : right.height()) + 1;
    }

    /**
     * 添加节点
     *
     * @param node 节点
     */
    public void add(Node node) {
        if (node == null) {
            return;
        }
        //比当前节点小,放到左边
        if (node.getValue() <= this.value) {
            if (this.left != null) {
                this.left.add(node);
            } else {
                this.left = node;
            }
        } else {
            //比当前节点大,放在右边
            if (this.right != null) {
                this.right.add(node);
            } else {
                this.right = node;
            }
        }
    }

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

    /**
     * 查找节点
     *
     * @param value 节点值
     * @return 查找的节点
     */
    public Node search(int value) {
        if (this.value == value) {
            return this;
        }
        //向左子树查找
        if (value < this.value && this.left != null) {
            return this.left.search(value);
        } else if (value >= this.value && this.right != null) {
            return this.right.search(value);
        } else {
            return null;
        }
    }

    /**
     * 查找节点的父节点
     *
     * @param value 查找的节点值
     * @return 父节点
     */
    public Node searchParent(int value) {
        if ((this.left != null && this.left.value == value) || (this.right != null && this.right.value == value)) {
            return this;
        }
        //查找的值小于当前节点的值,向左查找
        if (value < this.value && this.left != null) {
            return this.left.searchParent(value);
        } else if (value >= this.value && this.right != null) {
            //大于当前节点的值,向右查找
            return this.right.searchParent(value);
        } else {
            return null;
        }
    }
}
测试输出:
    中序遍历======
    3 4 5 6 7 8 
    节点4的高度:4
    节点6的高度:3
    节点3的高度:1
    
    
注意:平衡二叉树就是一种特殊的二叉排序树,只不过要求左右子树的高度差的绝对值不大于1
所以代码是在原始排序二叉树的基础上改造的,新增两个方法如下!
1.node节点中的获取节点高度
    class Node {
        private int value;
        //左节点
        private Node left;
        //右节点
        private Node right;
        /**
         * 获取以当前节点为根节点的数的高度
         * 这里使用递归一层一层加上来的!
         * @return 以当前节点为根节点的数的高度
         */
        public int height() {
            return Math.max(left == null ? 0 : left.height(),
                    right == null ? 0 : right.height()) + 1;
        }
        ...
   }
2.avltree代码
    class AvlTree {
        Node root;
        /**
         * 获取value节点为根节点的树的高度
         *
         * @param value 节点值
         * @return 以value节点为根节点的数的高度
         */
        public int height(int value) {
            //查找到以value的节点
            Node target = root.search(value);
            int height=0;
            if (target!=null){
                height=target.height();
            }
            return height;
        }
        ...
   }

左旋代码示例:

node节点代码:
    class Node {
         private int value;
        //左节点
        private Node left;
        //右节点
        private Node right;
        /**
         * 获取以当前节点为根节点的数的高度
         *
         * @return 以当前节点为根节点的数的高度
         */
        public int height() {
            return Math.max(left == null ? 0 : left.height(),
                    right == null ? 0 : right.height()) + 1;
        }

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

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

        /**
         * 左旋转代码
         */
        public void leftRoute() {
            //1.创建一个新的节点,节点值为当前根节点的值,并把新节点的左子树设置为当前节点的左子树
            //因为该刚发在avl数代码中用root节点调用的
            Node newRoot = new Node(this.value);
            newRoot.setLeft(this.left);
            //2.把新节点的右子树设置为当前节点右子树的左子树
            newRoot.setRight(this.right.left);
            //3.把当前节点的值改为当前节点右子树的值
            this.value = this.right.value;
            //4.把当前节点的右子树设置为右子树的右子树
            this.right = this.right.right;
            //5.当前节点的左子树设置为新节点
            this.left = newRoot;
        }
        /**
         * 添加节点
         *
         * @param node 节点
         */
        public void add(Node node) {
            if (node == null) {
                return;
            }
            //比当前节点小,放到左边
            if (node.getValue() <= this.value) {
                if (this.left != null) {
                    this.left.add(node);
                } else {
                    this.left = node;
                }
            } else {
                //比当前节点大,放在右边
                if (this.right != null) {
                    this.right.add(node);
                } else {
                    this.right = node;
                }
            }
            //重点:添加节点的时候进行判断是否需要左旋
            //如果右子树的高度-左子树高度 >1 时进行左旋
            if ((this.rightHeight() - this.leftHeight()) > 1) {
                leftRoute();
            }
        }
    }    
输出:
    public static void main(String[] args) {
        int arr[] = {4, 3, 6, 5,7, 8};
        AvlTree avlTree = new AvlTree();
        for (int i = 0; i < arr.length; i++) {
            avlTree.add(new Node(arr[i]));
        }
        System.out.println("中序遍历======");
        avlTree.infixOrder();
        System.out.println();
        System.out.println("节点6的高度:" + avlTree.height(6));
        System.out.println("节点4的高度:" + avlTree.height(4));
        System.out.println("节点7的高度:" + avlTree.height(7));
        System.out.println("节点3的高度:" + avlTree.height(3));
    }
    输出:发现高度降低了
        中序遍历======
        3 4 5 6 7 8 
        节点6的高度:3
        节点4的高度:2
        节点7的高度:2
        节点3的高度:1

双旋转思想

有时候左旋和右旋不能解决二叉树高度问题,如下数列

为什么会这样呢:

  1.当复合右旋条件时,当左子树的右子树高度(8的高度为2)大于左子树的高度(6的高度为1)==>就会出现上述问题

  解决办法

  2.先对当前节点的左子树进行左旋转

  3.再对当前节点的进行右旋转即可

  

代码示例:需要在Node类中新增节点时进行判断,左右是对称的
     /**
     * 添加节点
     *
     * @param node 节点
     */
    public void add(Node node) {
        if (node == null) {
            return;
        }
        //比当前节点小,放到左边
        if (node.getValue() <= this.value) {
            if (this.left != null) {
                this.left.add(node);
            } else {
                this.left = node;
            }
        } else {
            //比当前节点大,放在右边
            if (this.right != null) {
                this.right.add(node);
            } else {
                this.right = node;
            }
        }
        //如果右子树的高度-左子树高度 >1 时进行左旋
        if ((this.rightHeight() - this.leftHeight()) > 1) {
            //当右子树的左子树高度>右子树右子树的高度时,先进行右子树右旋,在整体左旋
            if (this.right != null && this.right.leftHeight() > this.right.rightHeight()) {
                this.right.rightRoute();
                this.leftRoute();
            } else {
                leftRoute();
            }
            return;
        }
        //如果左子树的高度-右子树高度 >1 时进行右旋
        if (this.leftHeight() - this.rightHeight() > 1) {
            //当根节点左子树的右子树高度大于根节点左子树的高度时,先对左子树进行左旋,在对整体进行右旋
            if (this.left != null && this.left.rightHeight() > this.left.leftHeight()) {
                this.left.leftRoute();
                this.rightRoute();
            } else {
                //其他直接进行右旋
                rightRoute();
            }
        }
    }
测试:
     public static void main(String[] args) {
        int arr[] = {10, 11, 7, 6, 8, 9};
        //int arr[] = {4,3,6,5,7,8};
        AvlTree avlTree = new AvlTree();
        for (int i = 0; i < arr.length; i++) {
            avlTree.add(new Node(arr[i]));
        }
        System.out.println("中序遍历======");
        avlTree.infixOrder();
        System.out.println();
        System.out.println("根节点:" + avlTree.root.getValue());
        System.out.println("树的高度:" + avlTree.root.height());
        System.out.println("左子树高度:" + avlTree.root.leftHeight());
        System.out.println("右子树高度:" + avlTree.root.rightHeight());
    }
输出:复合预期!
    中序遍历======
    6 7 8 9 10 11 
    根节点:8
    树的高度:3
    左子树高度:2
    右子树高度:2
总结:左旋右旋的思路
posted @ 2023-01-02 16:34  努力的达子  阅读(27)  评论(0编辑  收藏  举报