AVL樹


AVL(Adelson-Velskii and Landis)樹是帶有平衡條件(balance condition)的二叉查找樹。這個平衡條件必須容易保持,而且必須保證樹的深度是O(logN)。AVL樹規定其每個結點的左子樹和右子樹的高度最多差1。如下圖,左邊的樹是AVL樹,右邊的則不是。

AVL樹的每個結點(在其結點結構中)保留高度信息。可以證明,一般情況下,一棵AVL樹的高度最多為1.44log(N+2) – 1.328,並且實際上的高度只比logN稍微多一點。

下圖顯示了一棵具有最少結點(143)高度為9的AVL樹。這棵樹的左子樹是高度為7且結點數最少的AVL樹,右子樹是高度為8且結點數最少的AVL樹。可以看出在高度為h的AVL樹中,最少結點數S(h)=S(h-1) + S(h-2) + 1。對於h=0,S(h) = 1;h=1,S(h)=2。函數S(h)與斐波那契數密切相關,由此可以推出上面的關於AVL樹的高度的界。

除了插入操作(假設刪除是懶惰刪除),所有的AVL樹操作都可以以時間O(logN)執行。當進行插入操作時,需要更新通向根節點路徑上的那些結點的平衡信息,因為插入操作可能會破壞AVL樹的平衡特性。如果AVL樹的平衡被破壞,就要進行修正,這里稱其為旋轉(rotation)。

假設結點a的左子樹和右子樹失去平衡(高度差2),則可能的情況有下面4種:

1. 對a的左兒子的左子樹進行一次插入。

2. 對a的左兒子的右子樹進行一次插入。

3. 對a的右兒子的左子樹進行一次插入。

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

情形1和4關於結點a對稱,情形2和3關於結點a對稱,所以理論上只有兩種情況,不過從編程的角度來看還是4種情形。

第一種情況是插入發生在“外邊”的情形(即“左-左”或“右-右”),該情況通過對樹的一次單旋轉(single rotation)完成調整。第二種情況是插入發生在“內部”的情形(即“左-右”或“右-左”),該情況通過稍微復雜些的雙旋轉(double rotation)完成調整。

單旋轉

下圖顯示了對於情形1如何進行單旋轉。左邊是旋轉前,右邊是旋轉后。在左邊的樹中,子樹X中新插入了一個結點使得結點k2不再滿足AVL平衡性質,因為它的左子樹比右子樹深2層(圖中間的虛線代表樹的各層)。

上圖中為了使樹恢復平衡,把子樹X上移了一層,把子樹Z下移了一層。這其實已經超額完成了AVL性質的要求。抽象的形容一下單旋轉的過程:把樹看成是柔軟靈活的,抓住子結點k1,用力搖動它,在重力的作用下,k1就變成了新的根。X和Z仍然分別是k1的左兒子和k2的右兒子。子樹Y包含原樹中介於k1和k2之間的那些結點,現在把它放在新樹中k2的左兒子的位置上。這樣,所有對順序的要求都得到滿足,旋轉后得到的新樹是一棵AVL樹。不僅如此,其實上圖中經過單旋轉所得到的新樹的高度,和插入新節點(導致原樹不平衡)之前原樹的高度相同,所以通向根節點的路徑上的那些結點不需要進一步調整(它們仍然是平衡的)。下圖是一次單旋轉的例子:

下圖演示應於“情形4”的單旋轉修正:

下面演示一個更長一些的例子,從初始的空AVL樹開始插入3、2和1,然后依序插入4到7。在插入1時,AVL性質在根處被破壞。於是在根結點與其左兒子之間執行一次單旋轉以修正這個問題。

圖中以虛線連接的那兩個結點是旋轉的主體。接着插入4和5,在插入5時破壞了結點3處的AVL性質,通過單旋轉將其修正。在編程的時候必須記住:結點2的右兒子必須重新設置以鏈接到4而不是3,這一點很容易忘記,從而導致樹被破壞。

接着插入6,根節點處失去平衡,因為它的左子樹高度為0而右子樹高度為2。在根節點2和結點4之間執行一次單旋轉。

接着插入7,再次執行單旋轉。

 

雙旋轉

對於平衡二叉樹失衡的情況2、3,使用單旋轉是無效的。

此時需要使用雙旋轉(兩次旋轉:“左-右”或“右-左”) 

 

上圖中,樹B或樹C中有一棵比D深兩層。為了重新平衡,選擇k2作為新的根,k1作k2的左兒子,k3作k2的右兒子。與單旋轉一樣,把樹恢復到插入新節點之前的水平。下圖是“右-左”雙旋轉。

 

繼續單旋轉時的示例,以倒序插入10-16,接着插入8、9。插入16時並不破壞樹的平衡。插入15時引起結點7的不平衡,這屬於情況3,需要做一次“右-左”雙旋轉。

接着插入14,同樣也需要做一次“右-左”雙旋轉。結合上面的“右-左雙旋轉修正情形3”示意圖,子樹A的根結點為5,子樹B為空樹,子樹C的根結點為14,子樹D的根結點為16。

 插入13,在根結點處失衡。由於13不在4和7之間,所以是上面介紹的失衡情況4(對失衡結點4的右兒子的右子樹進行一次插入),這里需要做一次單旋轉(左旋)。

 插入12也需要做一次單旋轉(右旋)。

 接着插入11、10、8(未作旋轉圖示),得到下面一棵近乎理想的平衡樹。

 最后插入9,需要在8、9、10三個結點之間做“左-右”雙旋轉。

在編程時,需要計算子樹的高度差。有些程序員會在結點的存儲結構中,增加一個BF成員(Balance Factor:平衡因子)。這樣可以避免平衡因子的重復計算,提高程序的性能,不過卻喪失了程序的某些簡明性。

下面是一部分例程:

struct AvlNode
{
    Comparable element;
    AvlNode   *left;
    AvlNode   *right;
    int       height;

    AvlNode( const Comparable & theElement, AvlNode *lt,
                                            AvlNode *rt, int h = 0 )
      : element( theElement ), left( lt ), right( rt ), height( h ) { }
};

/**
 * Return the height of node t or -1 if NULL.
 */
int height( AvlNode *t ) const
{
    return t == NULL ? -1 : t->height;
}

/**
 * Internal method to insert into a subtree.
 * x is the item to insert.
 * t is the node that roots the subtree.
 * Set the new root of the subtree.
 */
void insert( const Comparable & x, AvlNode * & t )
{
    if( t == NULL )
        t = new AvlNode( x, NULL, NULL );
    else if( x < t->element )
    {
        insert( x, t->left );
        if( height( t->left ) - height( t->right ) == 2 )
            if( x < t->left->element )
                rotateWithLeftChild( t );
            else
                doubleWithLeftChild( t );
    }
    else if( t->element < x )
    {
        insert( x, t->right );
        if( height( t->right ) - height( t->left ) == 2 )
            if( t->right->element < x )
                rotateWithRightChild( t );
            else
                doubleWithRightChild( t );
    }
    else
        ;  // Duplicate; do nothing
    t->height = max( height( t->left ), height( t->right ) ) + 1;
}

下面是單旋轉函數rotateWithLeftChild的示意圖和源碼。rotateWithRightChild是對稱的,所以未貼出。

 

/**
 * Rotate binary tree node with left child.
 * For AVL trees, this is a single rotation for case 1.
 * Update heights, then set new root.
 */
void rotateWithLeftChild( AvlNode * & k2 )
{
    AvlNode *k1 = k2->left;
    k2->left = k1->right;
    k1->right = k2;
    k2->height = max( height( k2->left ), height( k2->right ) ) + 1;
    k1->height = max( height( k1->left ), k2->height ) + 1;
    k2 = k1;
}

下面是“左-右”雙旋轉函數doubleWithLeftChild示意圖和源代碼,doubleWithRightChild是對稱的。

/**
 * Double rotate binary tree node: first left child
 * with its right child; then node k3 with new left child.
 * For AVL trees, this is a double rotation for case 2.
 * Update heights, then set new root.
 */
void doubleWithLeftChild( AvlNode * & k3 )
{
    rotateWithRightChild( k3->left );
    rotateWithLeftChild( k3 );
}

Reference:

[1] 《數據結構與算法分析c++描述》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM