AVL树

理论介绍

AVL(Adelson-Velskii和Landis)树是带有平衡条件(balance condition)的二叉查找树。AVL树的核心是平衡因子(Balance Factor)这个概念,其数学原理是bf(x) = h(x-right) - h(x-left) .这个概念由一个放宽的平衡条件推导出来。
AVL树中最理想的平衡条件是要求每个节点都必须拥有相同高度的左子树和右子树。我们将空子树的高度定义为 -1 , 那么只有具有2^k - 1 个节点的理想平衡树(Perfectly balanced tree)满足这个条件。这种平衡条件下,构造出的AVL树的深度最小,但是其条件太严格以至于无法使用。因此,可以适当的放宽条件。
定义放宽后的平衡条件:每个节点的左子树和右子树的高度最多相差1(空树的高度定义为-1)。可以证明,在这种条件下,一棵AVL树的高度最多为1.44*log(N+2) - 1.328, 其实际高度略大于log N 。在高度为h的AVL树中,最少节点数S(h)由 S(h) = S(h-1) + S(h-2) + 1 近似给出。函数S(h)与斐波那契数密切相关,因此可以根据这个公式推导AVL树的高度的界。
在插入操作时,需要更新通向根节点路径上节点的所有平衡信息。其难点在于插入一个节点可能破坏AVL树的特性,那么此时需要恢复平衡的性质。把需要平衡的节点称作为A. 任意节点最多拥有两个子节点,因此出现不平衡情况下左右节点的高度差为2. 不难看出,这种不平衡状况可能出现在以下四种状况:

  1. 对A节点的左儿子的左子树执行一次插入操作
  2. 对A节点的左儿子的右子树执行一次插入操作
  3. 对A节点的右儿子的左子树执行一次插入操作
  4. 对A节点的右儿子的右子树执行一次插入操作

其中1,4为镜像操作,2、3为镜像操作。将1,4情况称之为左-左(LL)或右-右(RR)情况,这种情况通过对树的一次单旋转(Single Rotation)完成调整;将2,3称之为左-右(LR)或右-左(RL)情况,这种情况通过对树进行复杂的双旋转(Double Rotation)完成调整。下面将对这些旋转进行描述,并实现。


单旋转

下图显示了单旋转如何调整LL情况,其中左半部分为旋转前,右半部分为旋转后。由于其左子树比右子树深2层(图中几条实线表示树的深度),节点k2不满足平衡性质。在执行一次插入操作之前k2时满足平衡条件的,在k2的左孩子的左子树中插入一个节点后,子树X长出一层,这就使得子树X比子树Z深2层。此时Y不可能与新的X在同一水平线上,否则在执行插入操作之前k2就已经失去了平衡;同时,Y与Z也不在同一层,否则,k1就是通向根的路径上破坏AVL平衡条件的第一个节点。
avl-ll-00
要使k2恢复平衡,我们的做法是: 将X上移一层,并将Z下移一层。其实质就是重排节点形成一棵平衡等价树,如上图右半部分所示,该树需要满足平衡条件,同样在查找树顺序性质上与左半部分等价。根据二叉查找树的性质得出结论:重排序后k1变成新的根,由于k2>k1,新树中k2为k1的右孩子,X和Z仍旧是k1的左孩子和k2的右孩子,Y子树包含了介于k1和k2之间的节点,可以将Y子树放在k2左子树的位置上。这样做的结果就是,得到另外一个新的平衡二叉查找树,在变换期间,X上移一层,Z下移一层,Y高度不变,从而使得k1和k2节点满足平衡条件,并且它们的左右子树处于同一高度上。更进一步,由于一次插入操作使得X的高度增加,但是经过旋转等价变换整棵树的高度没有发生变化,因此**通向根节点的路径上节点的高度不需要进一步修正,因此也不需要进一步的旋转操作。
进一步理解单旋转操作,如下图所示
avl-ll-01
上图所示为LL构型,在B节点的左子树上插入节点导致A节点失衡,调整过程为:以B节点为轴心,A节点右旋至B的右子树,A的右子树又B的右子树代替。通过右旋操作,返回以B为根的平衡子树。
对于RR构型的操作(如下图所示)与LL构型操作是镜像的,所做的旋转无非就是以k1为轴心进行左旋,k1的左子树被k2取代,k2的右子树被k1的左子树Y取代,形成以k1为根的新的平衡子树。
avl-rr-00


双旋转

双旋转的出现是一位单旋转没有办法解决LR和RL情况。
avl-lr-0-1
如上图所示,在k1的右子树中进行一次插入操作,使得k2失衡,无论做何种单旋操作都没有办法解决等价新树根节点失衡的问题。对于类似与下图(或者是与下图镜像类似的情形),问题在于子树太深,造成单旋转没有办法降低子树深度。对于上述问题,问题的关键在于LR(或RL)构型造成k2节点失衡的原因是k1的左子树(或k1的右子树)深度加深,这与LL(RR)构型恰好相反,这使得我们没有办法通过一次单旋完成再平衡。解决问题的关键在于是否能够将LR(或RL)转化成LL(或RR)构型。答案是肯定的。如下图所示,我们将k1的子树Y更加细化为根k3与子树A,B.
alv-lr-00
一个很容易理解的方式:通过两次单旋来解决问题。首先以k3为轴进行左旋,其结果如上图中部图形所示,此时可以看到相应的结果形成了LL构型, 因此再进行一次右旋即可形成上图右侧部分的平衡结果。因此,我们可以看出,所谓的双旋其本质就是两次单旋的结合。由于单旋并不改变到根节点路径上节点的深度,此时进行两次单旋仍旧不改变其深度,因此,无需对通往根节点路径上节点重复进行双旋操作。


删除操作

单独讨论删除操作是因为该操作相对复杂。首先待删除的节点可能处于多种情况(这里假设节点是存在于树中的)。

  1. 节点为叶子 可以直接删除
  2. 节点左孩子(右孩子)不为空,但右孩子(左孩子)为空 删除节点后,左孩子(右孩子)替代该节点
  3. 节点两个孩子都存在 一般会采用的策略是用该节点右子树中最小的节点替代该节点(根据排序树的性质,右子树上最小节点一定没有左孩子,实际删除的是该节点中序遍历的前驱)

该删除操作可能会导致其父节点的高度发生变换,从而导致父节点失衡;同时,对其父节点进行调整后导致其父节点为根的子树高度发生变化,进而影响更上层节点平衡条件,从而失衡现象向其父节点传播。如上图一,图五,在旋转变换后k2节点的高度都可能降低,以至于k2的祖先节点发生失衡,此时需要依次进行旋转调整。


代码实现

import java.util.Comparator;
public class AVLTree<T> {
    /**
     * AVL node definition
     * @param <T>
     */
    private static class AVLNode<T>{
        //Constructors
        AVLNode(T x){
            //AVLNode(x, null, null);
            this(x, null, null);
        }
        AVLNode(T x, AVLNode<T> left, AVLNode<T> right){
            this.element = x;
            this.left = left;
            this.right = right;
            this.height = 0;
        }
        T element; // The data in node
        AVLNode<T> left; // Left child
        AVLNode<T> right; //Right child
        int height; //Height of the node
    }
    private AVLNode<T> root;
    // a more flexable way to define comparation
    private Comparator<? super T> cmp;
    public AVLTree(){
        this(null);
    }
    public AVLTree(Comparator<? super T> cmp){
        this.root = null; this.cmp = cmp;
    }
    /**
     * Judge internal comparable object.
     * @param lc
     * @param rc
     * @return
     */
    private int compare(T lc, T rc){
        if(null != cmp){
            return cmp.compare(lc, rc);
        }else{
            return ((Comparable)lc).compareTo(rc);
        }
    }
    public boolean isEmpty(){
        return null != root;
    }
    public void makeEmpty(){
        root = null;
    }
    public boolean contains(T x){
        return contains(x, root);
    }
    /**
     * Find the minimum item in the tree
     * @return minimum item in the tree , or null if the tree is an empty tree
     */
    public T findMin(){
        if(isEmpty()){
            return null;
        }
        // TODO
        return findMin(root).element;
    }
    /**
     * Find the maximum item in the tree
     * @return maximum item in the tree , or null if the tree is an empty tree
     */
    public T findMax(){
        if(isEmpty()){
            return null;
        }
        //TODO
        return findMax(root).element;
    }
    /**
     * Intert one node
     * @param x
     */
    public void insert(T x){
        //TODO
        root = insert(x, root);
    }
    /**
     * remove one item if it exists
     * @param x
     */
    public void remove(T x){
        //TODO
        AVLNode<T> delNode = remove(x, root);
    }
    /**
     * print the tree through inorder traversal
     */
    public void printTree(){
        //TODO
        inOrder(root);
    }
    /**
     * Private method to find whether an item x exists or not
     * @param x item to judge
     * @param _root root of the tree
     * @return true the item in the tree; false  the item is not in the tree
     */
    private boolean contains(T x, AVLNode<T> _root){
        if(_root == null){
            return false;
        }
        int compareResult = compare(x, _root.element) ;
        if(compareResult == 0){
            return true;
        }else if(compareResult < 0){
            return contains(x, _root.left);
        }else{
            return contains(x, _root.right);
        }
    }
    /**
     * Find the minimum item in the tree
     * @param _root root of the tree
     * @return minimum item in the tree , or null if the tree is an empty tree
     */
    private AVLNode<T> findMin(AVLNode<T> _root){
        if(_root == null){
            return null;
        }else if(_root.left == null){
            return _root;
        }
        return findMin(_root.left);
    }
    /**
     * Find the maximum item in the tree
     * @param _root root of the tree
     * @return minimum item in the tree , or null if the tree is an empty tree
     */
    private AVLNode<T> findMax(AVLNode<T> _root){
        if(_root == null){
            return null;
        }else if(_root.right == null){
            return _root;
        }
        return findMax(_root.right);
    }
    /**
     * Internal method to insert into a subtree.
     * @param x the item to insert
     * @param _root root of the subtree
     * @return new root of the subtree
     */
    private AVLNode<T> insert(T x, AVLNode<T> _root){
        //TODO
        if(_root == null){ //  the subtree is empty
            return new AVLNode<T>(x, null, null);
        }
        int comparaResult = compare(x, _root.element);
        if(comparaResult < 0 ){ // insert into left subtree
            _root.left = insert(x, _root.left);
            if(height(_root.left) - height(_root.right) == 2){ // balanced or not
                if(compare(x, _root.left.element) < 0){ // LL
                    _root = rotateWithLeftChild(_root);
                }else{ //LR
                    _root = doubleRotateWithLeftChild(_root);
                }
            }
        }else if(comparaResult > 0){ // insert into right subtree
            _root.right = insert(x, _root.right);
            if(height(_root.right) - height(_root.left) == 2){
                if(compare(x, _root.right.element) > 0){ // RR
                    _root = rotateWithRightChild(_root);
                }else{ // RL
                    _root = doubleRotateWithRightChild(_root);
                }
            }
        } // comparaResult == 0 ; Duplicate , do nothing
        _root.height = Math.max(height(_root.left), height(_root.right)) + 1; // recompute height
        return _root;
    }
    /**
     * Internal method to delete a node
     * @param x item to delete
     * @param _root root of the subtree
     * @return new root
     */
    private AVLNode<T> remove(T x, AVLNode<T> _root){
            if(_root == null){
                return null;
            }
            int comparaResult = compare(x, _root.element);

            if(comparaResult < 0){ // in left subtree
                _root.left = remove(x, _root.left);

            }else if(comparaResult > 0){ // in right subtree
                _root.right = remove(x, _root.right);

            }else { // it's the item
                if(_root.left != null && _root.right != null){ // nether left or right subtree is null
                    // replace the node which will be deleted with the minimum node in its right subtree
                    AVLNode<T> tmp = findMin(_root.right);
                    _root.element = tmp.element;
                    _root.right = remove(tmp.element,_root.right); // recursively delete
                }else{ // left or right subtree is null
                    if(_root.left == null){
                        _root = _root.right;
                    }else{
                        _root = _root.left;
                    }
                }
            }
            if(_root != null) { // rebuild balance
                if (height(_root.left) - height(_root.right) >= 2) { // LL or LR
                    if (height(_root.left.left) > height(_root.left.right)) {
                        _root = rotateWithLeftChild(_root.left);
                    } else {
                        _root = doubleRotateWithLeftChild(_root.left);
                    }
                } else if (height(_root.right) - height(_root.left) >= 2) { // RR or RL
                    if (height(_root.right.right) > height(_root.right.left)) { // RR
                        _root = rotateWithRightChild(_root.right);
                    } else { // RL
                        _root = doubleRotateWithRightChild(_root.right);
                    }
                }
                _root.height = Math.max(height(_root.right), height(_root.left)) + 1;
            }
            return _root;
    }
    /**
     * Rotate binary tree node with left child.
     * This is a single rotation for LL, for AVL tree
     * Update height and return new root
     * @param k2 root
     * @return new root
     */
    private AVLNode<T> rotateWithLeftChild(AVLNode<T> k2){
        AVLNode<T> k1 = k2.left;
        k2.left = k1.right;
        k1.right = k2;
        k1.height = Math.max(height(k1.left), height(k1.right)) + 1;
        k2.height = Math.max(height(k2.left), height(k2.right)) + 1;
        return k1;
    }
    /**
     * Double rotate binary tree node : first left child with its right child, RR;
     * then node k2 with new left child. This is a double ratation for LR for AVL tree.
     * Update heights then return new root
     * @param k2 root
     * @return new root
     */
    private AVLNode<T> doubleRotateWithLeftChild(AVLNode<T> k2){
        k2.left = rotateWithRightChild(k2.left);
        return rotateWithLeftChild(k2);
    }
    /**
     * Rotate binary tree node with right child
     * This is a single rotation for RR, for AVL tree
     * Update height and return new root
     * @param k2 root
     * @return new root
     */
    private AVLNode<T> rotateWithRightChild(AVLNode<T> k2){
        AVLNode<T> k1 = k2.right;
        k2.right = k1.left;
        k1.left = k2;
        k1.height = Math.max(height(k1.left), height(k1.right)) + 1;
        k2.height = Math.max(height(k2.left), height(k2.right)) + 1;
        return k1;
    }
    /**
     * Double rotate binary tree node : first right child with its left child LL,
     * then node k2 with new right child, RR . This is a double ratation for RL for AVL tree.
     * Update heights then return new root
     * @param k2 root
     * @return new root
     */
    private AVLNode<T> doubleRotateWithRightChild(AVLNode<T> k2){
        k2.right = rotateWithLeftChild(k2.right);
        return rotateWithRightChild(k2);
    }
    /**
     * Return the height of node t, or -1 if null
     * @param t node
     * @return
     */
    private int height(AVLNode<T> t){
        return t == null ? -1 : t.height;
    }
    /**
     * Inorder traversal every element
     * @param _root
     */
    private void inOrder(AVLNode<T> _root){
        if(_root != null){
            inOrder(_root.left);
            System.out.printf("Elem = %d, Height = %d \n",_root.element, _root.height);
            inOrder(_root.right);
        }
    }
}

测试

import org.junit.Assert;
import org.junit.Test;
import static org.junit.Assert.*;
public class AVLTreeTest {
    private AVLTree<Integer> _root = new AVLTree<Integer>();
    @Test
    public void isEmpty() throws Exception {
        Assert.assertEquals(false, _root.isEmpty());
    }
    @Test
    public void makeEmpty() throws Exception {
        _root.makeEmpty();
        Assert.assertEquals(true, _root.isEmpty());
    }
    @Test
    public void contains() throws Exception {
        Assert.assertEquals(true, _root.contains(5));
        Assert.assertEquals(false, _root.contains(50));
    }
    @Test
    public void findMin() throws Exception {
        Assert.assertEquals(new Integer(1), _root.findMin());
    }
    @Test
    public void findMax() throws Exception {
        Assert.assertEquals(new Integer(34), _root.findMax());
    }
    @Test
    public void insert() throws Exception {
          int[] node = {1,4,5,6,4,2,7,34,2,7};
          for(int i : node){
                _root.insert(i);
          }
          System.out.println("第一次:");
          printTree();
          contains();
          findMax();
          findMin();
          remove();
        System.out.println("删除后:");
          printTree();

         _root.makeEmpty();
        int[] items = {1,4,2,3,5,6,10,7,8,9};
        for(int i : items){
            _root.insert(i);
        }
        System.out.println("第二次:");
        printTree();
        remove();
        System.out.println("删除后:");
        printTree();
    }
    @Test
    public void remove() throws Exception {
        _root.remove(4);
    }
    @Test
    public void printTree() throws Exception {
        _root.printTree();
    }
}


小结

本文中所有的实现都是递归实现的,当然还可以使用非递归实现的方式。AVL树仅仅是最基本的平衡树,还有更多优化的AVL树适用于不同的场景,例如, 伸展树,黑白树等等,这些需要更进一步理解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值