【二叉树的平衡策略】 AVL树

谈起AVL树,就要说起 AVL树的由来 。普通的二叉搜索树到底遇到了什么问题呢?它到底能解决什么问题呢?

 传统二叉搜索树存在以下问题

   1. 二分查找在大多数情况下很快,但是如果 这个二叉树生成的时候有顺序,就可能 单一的偏左,或者偏右,

成为普通的单链表查询方式。效率变低。

   AVL二叉树 提出的解决方案

    1.保留基本的搜索二叉树的规则

    2.新增 左右子树平衡因子,也就是说任意节点的左右子树的高度差不能超过1.

   在插入 过程中可能遇到 打破这种规则的,也就是说 节点插入后,新插入节点到 树根之间的节点存在 高度差超过1.

   此时为了保持树的平衡就需要 进行旋转

   抽象的说 一共有以下四种旋转的情况

     1. 在  节点的 左节点 的左子树插入节点 导致高度差超过1   (右旋)

    2. 在 节点  右节点 右子树 插入 节点  导致高度差超过1    ( 左旋)

    3. 在节点的 左 节点的右子树 插入节点 导致高度差超过1  ( 左 -右)

     4. 在节点的   右 节点的 左子树的 插入节点 导致高度差超过1  (右-左)

   具体的旋转和插入的代码如下:

import lombok.Data;

public class AVLTree<E extends Comparable<E>> {
    // 树根结点

    private Node<E> root;

    public Node<E> getRoot(){

        return root;
    }

    // 向树中插入数据
    public Node<E> add(E element) {
        return root = insert(element, root);
    }

    // 删除树中值为element的元素
    public Node<E> delete(E element) {
        return root = remove(element, root);
    }

    // 前序遍历树
    public void print() {
        print(root);
    }

    /**
     * 向树中插入数据
     *
     * @param element 数据的值
     * @param node    树的根结点
     * @return 返回插入数据后的树的根结点
     */
    private Node<E> insert(E element, Node<E> node) {
        if (node == null) {
            return new Node<>(element);
        }

        if (element.compareTo(node.element) < 0) {
            node.left = insert(element, node.left);
        } else if (element.compareTo(node.element) > 0) {
            node.right = insert(element, node.right);
        }

        calcHeight(node);
        return balance(node);
    }

    /**
     * 删除树中的元素
     *
     * @param element 要删除的元素值
     * @param node    树的根结点
     * @return 返回删除后的树根结点
     */
    private Node<E> remove(E element, Node<E> node) {
        if (node == null || (node.left == null && node.right == null)) {
            return null;
        }

        if (element.compareTo(node.element) < 0) {
            node.left = remove(element, node.left);
        } else if (element.compareTo(node.element) > 0) {
            node.right = remove(element, node.right);
        } else {
            if (node.right == null) {// 右空左不空
                node = node.left;
            } else if (node.left == null) {// 左空右不空
                node = node.right;
            } else {//左右都不空,则取出右子树最小结点,并用来替换根结点
                Node<E> rightMin = searchMin(node.right);
                node.element = rightMin.element;
                node.right = remove(rightMin.element, node.right);
            }
        }
        calcHeight(node);
        return balance(node);
    }

    /**
     * 打印以node为树根的树
     *
     * @param node 树根
     */
    private void print(Node<E> node) {
        if (node == null) {
            return;
        }
        System.out.println(node.element + " , height = " + node.height);
        print(node.left);
        print(node.right);
    }

    /**
     * AVL树的结点类
     *
     * @param <E>结点值的类型
     */
     @Data
     class Node<E> {
        E element;
        Node<E> left;
        Node<E> right;
        int height;

        public Node(E element) {
            this.element = element;
        }
    }

    private Node<E> searchMin(Node<E> node) {
        assert node != null;

        if (node.left != null) {
            return searchMin(node.left);
        }
        return node;
    }

    /**
     * 计算结点的高度
     *
     * @param node 要计算的节点
     * @return 返回节点高度
     */
    private int height(Node<E> node) {
        return node == null ? -1 : node.height;
    }

    private void calcHeight(Node<E> node) {
        node.height = Math.max(height(node.left), height(node.right)) + 1;
    }

    /**
     * 左旋
     *
     * @param node 要旋转的子树的根结点
     * @return 返回旋转后的子树的根结点
     */
    private Node<E> leftRotate(Node<E> node) {
        Node<E> newNode = node.right;
        node.right = newNode.left;
        newNode.left = node;
        calcHeight(node);
        calcHeight(newNode);
        return newNode;
    }

    /**
     * 右旋
     *
     * @param node 要旋转的子树的根结点
     * @return 返回旋转后的子树的根结点
     */
    private Node<E> rightRotate(Node<E> node) {
        Node<E> newNode = node.left;
        node.left = newNode.right;
        newNode.right = node;
        calcHeight(node);
        calcHeight(newNode);
        return newNode;
    }

    /**
     * 先左旋再右旋
     *
     * @param node 要旋转的子树的根结点
     * @return 返回旋转后的子树的根结点
     */
    private Node<E> leftAndRightRotate(Node<E> node) {
        node.left = leftRotate(node.left);
        return rightRotate(node);
    }

    /**
     * 先右旋再左旋
     *
     * @param node 要旋转的子树的根结点
     * @return 返回旋转后的子树的根结点
     */
    private Node<E> rightAndLeftRotate(Node<E> node) {
        node.right = rightRotate(node.right);
        return leftRotate(node);
    }

    /**
     * 让以node为根结点的树恢复平衡
     *
     * @param node 根结点
     * @return 返回恢复平衡后的树的根结点
     */
    private Node<E> balance(Node<E> node) {
//        assert node != null;
        if (height(node.left) - height(node.right) == 2) {
            if (height(node.left.left) > height(node.left.right)) {
                // 需要进行右旋转
                return rightRotate(node);
            } else {// 需要左旋再右旋
                return leftAndRightRotate(node);
            }
        } else if (height(node.right) - height(node.left) == 2) {
            if (height(node.right.right) > height(node.right.left)) {
                // 需要进行左旋转
                return leftRotate(node);
            } else {// 需要右旋再左旋
                return rightAndLeftRotate(node);
            }
        }
        return node;
    }
}

 

     测试代码:

import com.alibaba.fastjson.JSON;

/**
 *
 * AVL树测试
 *
 * */
public class AVLTest {

        public static void main(String[] args) {
            AVLTree<Integer> tree = new AVLTree<>();
            tree.add(3);
            tree.add(2);
            tree.add(1);
            tree.add(4);
            tree.add(5);
            tree.add(6);
            tree.add(7);
            tree.add(10);
            tree.add(9);
            tree.add(8);
            System.out.println(JSON.toJSON(tree.getRoot()));
           
        }
 
}

 此处只讲原理 ,实际应用中,大多都以红黑树 替代 AVL树。

  因为AVL树是对高度差及其敏感的,这样虽然能高度保证树的平衡。使查询效率高。但是也付出了代价,得不断的根据判断高度,调整平衡。 但是在删除的时候,这个导致在 删除元素的时候,最坏的时间复杂度 O(logN)可能导致 雪崩。

  因为 造成AVL删除雪崩的真正原因正是因为,他能容忍这个1的高度差。在高度差大量积累后,删除薄弱侧的节点,可能引起需要大量调整才能重新平衡的情况。

     红黑树的解决方案是 容忍不平衡 【增大容忍不平衡的情况】

红黑树的思路的核心是增大了可容忍的高度差,从而实现既保证查询效率(O(logN)),也保证了插入和删除后调整平衡的效率(O(1))。
红黑树的查询效率(2 * O(logN))是略低于AVL树(O(logN))的,但是红黑树通过牺牲了少许查询效率,使插入删除后的调整效率达到了常数级别。
红黑树算法中的着色策略、对于父节点、叔节点、祖父节点等等节点的颜色判断、以及相应的调整策略都是经过极度抽象后的结果,因此想要从头到尾彻底理解红黑树的设计思想其实还是有些难度的。

 顺便捎带说一句:

 这就是 Java 中 HashMap 中使用 红黑树做 数据结构的原因。

  有的得有舍,各种算法需要知道优缺点,还有演进路线。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值