zoukankan      html  css  js  c++  java
  • 32-平衡二叉树(AVL)

    1. 引入

    • 给你一个数列 {1,2,3,4,5,6},要求创建一颗二叉排序树(BST)
    • 上述 BST 存在的问题分析
      • 左子树全部为空,从形式上看,更像一个单链表
      • 插入速度没有影响
      • 查询速度明显降低,不能发挥 BST 的优势 // 因为每次还需要比较左子树,其查询速度比单链表还慢
    • [解决方案] 平衡二叉树(AVL)

    2. 平衡二叉树

    • 平衡二叉树也叫平衡二叉搜索树(Self-balancing binary search tree), 可以保证查询效率较高。
    • 特点
      • 它是一棵空树或它的左右两个子树的高度差的绝对值不超过 1
      • 左右两个子树都是一棵平衡二叉树
      • 注意,平衡二叉树中结点与其左右子结点仍旧满足 [二叉排序树] 的关系
    • 平衡二叉树的常用实现方法
      • 红黑树
      • AVL
      • 替罪羊树
      • Treap
      • 伸展树
    • 举例说明

    3. 思路分析

    3.1 单旋转

    • 左旋转

    • 右旋转

    3.2 双旋转

    单旋转可能引发的问题

    当符合旋转条件 右子树高度 - 左子树高度 > 1 时,要先做判断!若其中 {右子树的左子树} 高度 > {右子树的右子树} 高度,则要先对 [当前结点的右子结点] 进行“右旋转”,再对 [当前结点] 进行“左旋转”的操作。

    当符合旋转条件 左子树高度 - 右子树高度 > 1 时,要先做判断!若其中 {左子树的右子树} 高度 > {左子树的左子树} 高度,则还要先对 [当前结点的左子结点] 进行“左旋转”,再对 [当前结点] 进行“右旋转”的操作。

    4. 代码实现

    public class AVLTreeDemo {
        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}; // 双旋转(先左再右)
            int[] arr = {10, 4, 17, 12, 18, 11}; // 双旋转(先右再左)
            AVLTree tree = new AVLTree();
            for(int i = 0; i < arr.length; i++)
                tree.add(new Node(arr[i]));
            tree.infixOrder();
            System.out.printf("[AVL] 树的高度:%d, 左子树的高度:%d, 右子树的高度:%d
    "
                , tree.root.height(), tree.root.leftHeight(), tree.root.rightHeight());
            System.out.println("root: " + tree.root);
        }
    }
    
    class AVLTree  {
        public Node root;
    
        public void add(Node node) {
            if (root == null) root = node;
            else root.add(node);
        }
    
        public void infixOrder() {
            if (root != null) root.infixOrder();
            else System.out.println("空树");
            System.out.println();
        }
    }
    
    class Node {
        public int value; // 结点权值
        public Node left;
        public Node right;
    
        public Node(int value) {
            this.value = value;
        }
    
        // 返回以该结点为根结点的子树的高度
        public int height() {
            // + 1: Math.max统计的是子树高度, 根结点本身也算 1 层
            return Math.max(left == null ? 0 : left.height(),
                    right == null ? 0 : right.height()) + 1;
        }
    
        // 返回左子树的高度
        public int leftHeight() {
            return left == null ? 0 : left.height();
        }
    
        // 返回右子树的高度
        public int rightHeight() {
            return right == null ? 0 : right.height();
        }
    
        public void leftRotate() {
            // 1. 创建 [新的结点], 其 value 为 [当前结点] 的value
            Node newNode = new Node(value);
            // 2. [新的结点] 的左子结点 = [当前结点] 的左子结点
            newNode.left = left;
            // 3. [新的结点] 的右子结点 = [当前结点] 的右子结点的左子结点
            newNode.right = right.left;
            // 4. [当前结点] 的 value = 其右子结点的value
            value = right.value;
            // 5. [当前结点] 的右子结点 替换为 其右子结点的右子结点
            right = right.right;
            // 6. [当前结点] 的左子结点 替换为 [新的结点]
            left = newNode;
        }
    
        public void rightRotate() {
            // 1. 创建 [新的结点], 其 value 为 [当前结点] 的value
            Node newNode = new Node(value);
            // 2. [新的结点] 的右子结点 = [当前结点] 的右子结点
            newNode.right = right;
            // 3. [新的结点] 的左子结点 = [当前结点] 的左子结点的右子结点
            newNode.left = left.right;
            // 4. [当前结点] 的 value = 其的左子结点的value
            value = left.value;
            // 5. [当前结点] 的左子结点 替换为 其左子结点的左子结点
            left = left.left;
            // 6. [当前结点] 的右子结点 替换为 [新的结点]
            right = newNode;
        }
    
        // (递归)添加结点
        public void add(Node node) {
            if (node == null) return;
            // 判断 node 的 value 和当前子树根结点的 value 的大小关系
            if (node.value < 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;
            }
    
            // 当添加一个结点后,递归返回过程中都得查看当前树结构是否还能满足 AVL 树结构
            // a. 右子树 > 左子树 → 左旋转
            if (rightHeight() - leftHeight() > 1) {
                // 右子树的左子树 > 右子树的右子树
                if (right.leftHeight() > right.rightHeight())
                    right.rightRotate(); // 得先来一次右旋转
                leftRotate();
                return;
            }
    
            // b. 左子树 > 右子树 → 右旋转
            if (leftHeight() - rightHeight() > 1) {
                // 左子树的右子树 > 左子树的左子树
                if (left.rightHeight() > left.leftHeight())
                    left.leftRotate(); // 得先来一次左旋转
                rightRotate();
            }
        }
    
        // 中序遍历
        public void infixOrder() {
            if (this.left != null) this.left.infixOrder();
            System.out.print(this + " ");
            if (this.right != null) this.right.infixOrder();
        }
    
        @Override
        public String toString() {
            return "[value=" + value + "]";
        }
    }
    

    // 上述代码仅是在添加结点过程中实现平衡二叉树,删除结点后维系平衡二叉树的代码木有

  • 相关阅读:
    UUID工具类
    jax-rs 标准以及 结合 resteasy的使用
    Mina.Net实现的断线重连
    Mina.Net实现的UDP协议消息收发Demo
    MySql 比Replace Into更适合的用法,外加SqlServer的方式。
    MySql【Insert Select Not Exist】判断记录再添加值的方案
    MySql中存储过程中的@变量总是无法执行,提示Parameter '@XXX' must be defined
    Go语言使用Beego的ORM插入Mysql后,时区不一致的解决方案
    Go语言中Path包用法
    C#(WPF和WinForm)在普通类中调用到主线程的方法,SynchronizationContext的用法。
  • 原文地址:https://www.cnblogs.com/liujiaqi1101/p/12327992.html
Copyright © 2011-2022 走看看