一種簡單的平衡樹-AVL樹

AVL 樹

AVL樹(Adelson-Velskii 和 Laandis)樹是帶有平衡條件(balance condition)的二叉查找樹。這個平衡條件必需要容易保持,並且他保證樹的深度必須是 O(log N)。最簡單的想法是要求左右子樹具備相同的高度。java

clipboard.png

另外一種平衡條件是要求每一個節點都必須有相同高度的左子樹和右子樹。若是空子樹的高度定義爲 -1,那麼只有具備 2k-1 個節點的理想平衡樹(perfectly balanced tree)知足這個條件。所以,雖然這種平衡條件保證了樹的深度小,可是太嚴格難以使用。ide

AVL 樹是其每一個節點的左子樹和右子樹的高度最多差 1 的二叉查找樹(空樹的高度定義爲 -1)。函數

clipboard.png

下圖是一棵具備最少節點(143)和高度爲 9 的 AVL 樹。這棵樹的左子樹是高度爲 7 且大小最小的 AVL 樹,右子樹是高度爲 8 且大小最小的 AVL 樹。他告訴咱們,在高度爲 h 的 AVL 樹種,最少節點數由this

  • S(h) = S(h - 1) + S(h - 2) + 1 給出spa

對於 h = 0,S(h) = 1;h = 1, S(h) = 2。函數 S(h) 與斐波那契數列密切相關。3d

clipboard.png

所以,出去可能的插入外(咱們將假設懶惰刪除),全部的樹操做均可以以時間 O(log N) 執行。當執行插入操做時,咱們須要更新通向根節點路徑上的那些節點的全部平衡信息,而插入操做隱含的困難的緣由在於,插入一個節點可能破壞 AVL 樹的特性。若是發生這種狀況,那麼就要在考慮這一步插入完成以前回復平衡的性質。
事實上,咱們能夠經過旋轉來對樹進行簡單的修正來作到。code

  • 在插入之後,只有那些從插入點到根節點的路徑上的節點的平衡可能被改變,由於只有這些節點的子樹可能發生變化。當咱們沿着這條路徑上行到根並更新平衡信息時,能夠發現一個節點,它的新平衡破壞了 AVL 條件。咱們將支出如何在第一個這樣的節點(即最深的節點)從新平衡這棵樹,並證實這一從新平衡保證整個樹知足 AVL 性質。blog

咱們把必須從新平衡的節點叫作 α。因爲任意節點最多隻有兩個兒子,所以出現高度不平衡就須要 α 點的兩顆子樹的高度差 2。這可能出如今一下四中狀況中:遞歸

  1. 對 α 的左兒子的左子樹進行一次插入;ip

  2. 對 α 的左兒子的右子樹進行一次插入;

  3. 對 α 的右兒子的左子樹進行一次插入;

  4. 對 α 的右兒子的右子樹進行一次插入。

情形 1 和 4 是關於 α 點的鏡像對稱,而 2 和 3 是關於 α 點的鏡像對稱。

第一種狀況是插入發生在 "外邊" 的狀況(即左-左的狀況或右-右的狀況),該狀況經過對樹的一次單旋轉(single rotation)而完成調整。第二種狀況是插入發生在 "內部" 的狀況(即左-右的狀況或右-左的狀況),該狀況經過稍微複雜一些的雙旋轉(double rotation)實現。

clipboard.png

情形可能以下所示:

/**
     * 考慮對以下的樹中插入數字 2
     * 
     *                 5
     *               /   \
     *             4      6
     *           /   \
     *         3       ?
     * 
     *                  4
     *               /     \
     *             3        5
     *           /        /   \
     *         2        ?      6
     * 
     * ? 表明可能存在也可能不存在,不會影響結果.而且,在這裏,咱們能夠將 ? 當作 5 的左節點,也能夠將 
     * ? 當作 3 的左節點
     * @param k2
     * @return
     */
  • k2不知足 AVL 平衡性質,由於他的左子樹比右子樹深 2 層(圖中間的虛線表示樹的各層)。該圖所描述的狀況是情形 1 的一種可能的狀況,在插入以前 k2知足 AVL 性質,可是在插入以後,子樹 X 長出一層,這使得他比子樹 Z 深出 2 層。Y 不可能與新 X 在同一水平上,由於那樣 k2 在插入之前就已經失去平衡了;Y 也不可能與 Z 在同一層上,由於那樣 k1 就會是在通向根路徑上破壞 AVL 平衡條件的第一個節點。

如今咱們觀察這棵樹,咱們會獲得一些結論:

kX < k1 < kY < k 2 < kZ,由於如今節點 X 的深度爲 2,因此節點 X 必須爲節點 1 的一個單獨的節點;
又由於 kY < k2 < kZ。因此可使用 k2 做爲根節點,kY 和 kZ 做爲 k2 的左節點和右節點生成新的 AVL 樹。
最後,以這棵新生成的 AVL 樹做爲 k1 的右節點。

clipboard.png

咱們沿着插入的節點上行,找到第一個特殊的節點,這個特殊的節點破壞了 AVL 樹的平衡;
若是是狀況 1,那麼咱們假設鏈接插入數據的節點和特殊節點的節點爲 k1,這個特殊的節點爲 k2。那麼,咱們如今只須要用 k1 當作新的根節點,左節點不變,同時將 k1 的右節點當作 k2 的左節點,並將 k2 做爲 k1 的新的右節點便可。
若是是狀況 4,那麼咱們這個特殊的節點爲 k1,鏈接插入數據的節點和特殊節點的節點爲 k2。那麼,咱們如今可使用 k2 做爲新的根節點,右節點不變,同時將 k2 的左節點當作 k1 的右節點,最後將 k1 做爲 k2 的新的左節點便可。

雙旋轉

clipboard.png

clipboard.png

clipboard.png

/**
     * 對於情形 2 的一次雙旋轉
     * 
     *                k3
     *               /   \
     *            k1      D
     *           /   \
     *         A       k2
     *
     * 插入節點 B 或 C,B 和 C 在插入任意一個的時候就已經破壞了 Avl 樹的平衡條件
     *
     *                k3
     *               /   \
     *            k1       D
     *           /   \
     *         A       k2
     *                / \
     *              B    C
     * 
     * k3.left = rotateWithRightChild(k3.left),以 k3.left 即 k1 爲根節點進行一次右旋轉
     * 
     *                k3
     *               /   \
     *            k2      D
     *           /   \
     *         k1       C
     *        / \         
     *      A      B    
     * 
     * rotateWithLeftChild(k3)
     * 
     *                  k2
     *               /      \
     *            k1        k3
     *           /   \       /  \
     *         A      B    C      D
     * 
     * @param k3
     * @return
     */

咱們注意到,在上面的圖中,k1 < k2 < k3,這迫使 k1 是 k2 的左兒子,k3 是 k2 的右兒子。因而,最後咱們獲得的結果就很明顯了。

須要旋轉的三個節點是:上行找到的第一個破壞 AVL 樹的節點,新的節點插入後的父節點,鏈接第一個破壞 AVL 樹的節點和新的節點的父節點

假設這三個節點分別是 k1,k2,k3,則 k1 和 k3 是根據插入的數據是狀況 2 或狀況 3 下變化的,而 k2 永遠都是那個被插入數據的節點。
在狀況 2 下,k1 是鏈接特殊節點和被插入數據節點的節點,k3 是特殊節點;
在狀況 3 下,k1 是特殊節點,k3 是鏈接特殊節點和被插入數據節點的節點;
在狀況 2 和 3 下,咱們都是使用 k2 做爲新的根節點,k1 做爲左兒子,k3 做爲右兒子;
隨後,咱們將 k2 原來的左子節點做爲 k1 的右子節點,k2 的原來的右子節點做爲 k3 的左子節點。

不管是單旋轉仍是雙旋轉,咱們都須要將獲得的新的 AVL 子樹添加到原來的樹中。

AVL 樹的插入方法的實現

爲了將項是 X 的一個新節點插入到一棵 AVL 樹 T 種去,咱們遞歸的將 X 插入到 T 相應的子樹(稱爲 TLR)中。若是 TLR 的高度不變,那麼插入完成。不然,若是在 T 中出現高度不平衡,則根據 X 以及 T 和 TLR 中的項左適當的單旋轉或雙旋轉,更新這些高度(並解決好與樹的其他部分的連接)從而完成插入。

package com.mosby.common.structure;

/**
 * AVL 樹
 */
public class AvlTree <T extends Comparable<? super T>> {
    private static  class AvlNode <T extends Comparable<? super T>>{
        AvlNode(T element){
            this(element, null, null);
        }
        AvlNode(T element, AvlNode<T> left, AvlNode<T> right){
            this.element = element;
            this.left = left;
            this.right = right;
            this.height = 0;
        }
        T element;
        AvlNode<T> left;
        AvlNode<T> right;
        int height;
        @Override
        public String toString(){
            return element.toString();
        }
    }
    public AvlTree(){
        root = null;
    }
    public void insert(T x){
        root = insert(x, root);
    }
    
    public void remove(T x){
        System.out.println("Sorry, remove unimplemented");
    }
    
    public T findMin(){
        return elementAt(findMin(root));
    }
    public T findMax(){
        return elementAt(findMax(root));
    }
    public T find(T x){
        return elementAt(find(x, root));
    }
    public void makeEmpty(){
        root = null;
    }
    
    public boolean isEmpty(){
        return root == null;
    }
    private T elementAt(AvlNode<T> t){
        return t == null ? null : t.element;
    }
    private AvlNode<T> findMin(AvlNode<T> t){
        if(t == null){
            return t;
        }
        while(t.left != null){
            t = t.left;
        }
        return t;
    }
    private AvlNode<T> findMax(AvlNode<T> t){
        if(t == null){
            return t;
        }
        while(t.right != null){
            t = t.right;
        }
        return t;
    }
    private AvlNode<T> find(T x, AvlNode<T> t){
        while(t != null){
            if(x.compareTo(t.element) < 0){
                t = t.left;
            }else if(x.compareTo(t.element) > 0){
                t = t.right;
            }else{
                return t;
            }
        }
        return null;
    }
    /**
     * 插入方法
     * @param x
     * @param t
     * @return
     */
    private AvlNode<T> insert(T x, AvlNode<T> t){
        if(t == null){
            return new AvlNode<T>(x);
        }else if(x.compareTo(t.element) < 0){
            t.left = insert(x, t.left);
            if(height(t.left) - height(t.right) == 2){
                if(x.compareTo(t.left.element) < 0){
                    t = rotateWithLeftChild(t);
                }else{
                    t = doubleWithLeftChild(t);
                }
            }
        }else if(x.compareTo(t.element) > 0){
            t.right = insert(x, t.right);
            if(height(t.right) - height(t.left) == 2){
                if(x.compareTo(t.right.element) > 0){
                    t = rotateWithRightChild(t);
                }else{
                    t = doubleWithRightChild(t);
                }
            }
        }else{
            //Duplicate; do nothing
        }
        t.height = max(height(t.left), height(t.right)) + 1;
        return t;
    }
    
    /**
     * 插入相關操做
     */
    private static <T extends Comparable<? super T>> int height(AvlNode<T> t){
        return t == null ? -1 : t.height;
    }
    private static int max(int lhs, int rhs){
        return lhs > rhs ? lhs : rhs;
    }
    /**
     * 對於情形 1 的一次旋轉
     * 
     *                 5
     *               /   \
     *             4      6
     *           /   \
     *         3        ?
     * 
     *                  4
     *               /      \
     *             3         5
     *           /         /   \
     *         2        ?        6
     * 
     * ? 表明可能存在也可能不存在,不會影響結果
     * @param k2
     * @return
     */
    private static <T extends Comparable<? super T>> AvlNode<T> rotateWithLeftChild(AvlNode<T> k2){
        //k2 是節點 5,k1 是節點 4
        AvlNode<T> k1 = k2.left;
        k2.left = k1.right;
        k1.right = k2;
        /**
         * 咱們能夠看到,節點中有三個節點的高度改變了:3,4,5.
         * 而咱們在最後插入時的函數棧以下所示:
         * 2, null
         * ------
         * 2,    3
         * ------
         * 2,    4
         * ------
         * 2,    5
         */
        k2.height = max(height(k2.left), height(k2.right)) + 1;
        k1.height = max(height(k1.left), height(k1.right)) + 1;
        return k1;
    }
    /**
     * 對於情形 4 的一次旋轉
     * 
     *                 5
     *               /   \
     *             4        6
     *                    /  \
     *                  ?      7
     * 
     * 
     *                  6
     *               /   \
     *             5        7
     *           /   \       \
     *         4        ?     8
     * 
     * @param k1
     * @return
     */
    private static<T extends Comparable<? super T>> AvlNode<T> rotateWithRightChild(AvlNode<T> k1){
        //k1 是節點 5,k2 是節點 6
        AvlNode<T> k2 = k1.right;
        k1.right = k2.left;
        k2.left = k1;
        /**
         * 節點中有 3 個節點的高度改變了:5,6,7
         * 在最後插入時的函數棧以下所示:
         * 8, null
         * ------
         * 8,   7
         * ------
         * 8,   6
         * ------
         * 8,   5
         */
        k1.height = max(height(k1.left), height(k1.right)) + 1;
        k2.height = max(height(k2.left), height(k2.right)) + 1;
        return k2;
    }
    
    /**
     * 對於情形 2 的一次雙旋轉
     * 
     *                k3
     *               /  \
     *            k1     D
     *           /   \
     *         A       k2
     *
     * 插入節點 B 或 C,B 和 C 在插入任意一個的時候就已經破壞了 Avl 樹的平衡條件
     *
     *                k3
     *               /  \
     *            k1     D
     *           /   \
     *         A       k2
     *                / \
     *              B       C
     * 
     * k3.left = rotateWithRightChild(k3.left),以 k3.left 即 k1 爲根節點進行一次右旋轉
     * 
     *                k3
     *               /  \
     *            k2     D
     *           /   \
     *         k1       C
     *        / \         
     *      A      B    
     * 
     * rotateWithLeftChild(k3)
     * 
     *                  k2
     *               /      \
     *            k1         k3
     *           /   \      /  \
     *         A      B   C     D
     * 
     * @param k3
     * @return
     */
    private static <T extends Comparable<? super T>> AvlNode<T> doubleWithLeftChild(AvlNode<T> k3){
        /**
         * k1 是節點 4,k2 是節點 5,k3 是節點 7
         * 咱們看到,在第一次旋轉中,有幾個節點的高度改變了:k二、k3
         * 在插入的時候,函數棧應該以下所示:
         * B|C  null
         * --------
         * B|C   k2
         * --------
         * B|C   k1
         * --------
         * B|C   k3
         */
        k3.left = rotateWithRightChild(k3.left);
        return rotateWithLeftChild(k3);
    }
    private static <T extends Comparable<? super T>> AvlNode<T> doubleWithRightChild(AvlNode<T> k1){
        k1.right = rotateWithLeftChild(k1.right);
        return rotateWithRightChild(k1);
    }
    private AvlNode<T> root;
    
    public static void main(String[] args) {
        AvlTree<Integer> avl = new AvlTree<Integer>();
        avl.insert(5);
        avl.insert(4);
        avl.insert(3);
        avl.insert(2);
        avl.insert(1);
        avl.insert(0);
    }
}

注意幾點:

  1. 所謂的 rotateLeft 和 rotateRight 是指旋轉左樹仍是右樹,而不是指向左旋轉仍是向右旋轉。例如 rotateLeft(t) 就是將 t.left 和 t 調換位置;

  2. 咱們在遞歸中已經修改了 height,爲何在旋轉的時候還要修改樹的高度呢?
    以左旋轉爲例子:

    \* 2, null
        \* ------
        \* 2,    3
        \* ------
        \* 2,    4
        \* ------
        \* 2,    5

    在這個函數棧中,4 相對於 5 先出棧,而且在遞歸的求 4 的高度的時候須要用到 5 的高度,而 5 在進行旋轉以後它的高度就已經改變了,因此咱們必須在旋轉後當即改變 5 的高度。

而 3 雖然它的高度改變了,可是 3 在棧中的位置決定了它在 null 出棧後即運行,且它僅僅會用到新插入的節點的高度,而新插入的節點的高度顯然是爲 0 的,因此它不須要作特別的處理,等出棧的時候計算它的高度就能夠了。

相關文章
相關標籤/搜索