平衡二叉樹 AVL 的插入節點後旋轉方法分析

s1mba發表於2013-10-28

平衡二叉樹 AVL( 發明者為Adel'son-Vel'skii 和 Landis)是一種二叉排序樹,其中每一個節點的左子樹和右子樹的高度差至多等於1。

首先我們知道,當插入一個節點,從此插入點到樹根節點路徑上的所有節點的平衡都可能被打破,如何解決這個問題呢?

這裡不講大多數書上提的什麼平衡因子,什麼最小不平衡子樹,實際上讓人(me)更加費解。實際上你首要做的就是先找到第一個出現不平衡的節點,也就是從插入點到root節點的路徑上第一個出現不平衡的節點,即深度最深的那個節點A,對以它為根的子樹做一次旋轉或者兩次旋轉,此時這個節點的平衡問題解決了,整個往上路徑經過的節點平衡問題也隨之解決。


注:AVL 樹也是一種二叉查詢樹,故刪除策略可以參照前面文章來實現,只是刪除節點後,如果平衡被打破,則也需要進行旋轉以保持平衡。

After deletion, retrace the path back up the tree (parent of the replacement) to the root, adjusting the balance factors as needed.


下面先來分析下不平衡發生的四種情況:

1、An insertion into the left subtree of the left child of A; (LL)

2、An insertion into the right subtree of the left child of A;(RL)

3、An insertion into the left subtree of the right child of A;(LR)

4、An insertion into the right subtree of the right  child of A;(RR)


旋轉方法:

1、A 和 A's child 順時針旋轉 singlerotateLL()

4、A 和 A's child 逆時針旋轉 singlerotateRR()

2、A's child 和 A's grandchild 逆時針旋轉,A 和 A's new child 順時針旋轉  doublerotateRL()

3、A's child 和 A's grandchild 順時針旋轉,A 和 A's new child 逆時針旋轉 doublerotateLR()

可以看出1,4對稱;2,3對稱,實際在實現 rotate 函式的時候實現1和4 即可,2和3直接呼叫1&4的實現。在實現1&4時需要傳遞需要旋轉的子樹的root node 作為引數,如 nodePtr doublerotateRL(A) { A->left = singlerotateRR(A->left);  return singlerotateLL(A);}  

當然,這樣說還是對應不上,下面上圖分析。


第一種情況舉例:


現在想要插入的點是6,請看是否符合第一種情況的描述。8是不是深度最深的發生不平衡的點?6是不是插入在A的左孩子的左子樹?符合是吧,那就直接按上述方法順時針旋轉7和8,效果是右圖。當然這只是邏輯上的檢視而已,真正函式實現也不難,就是修改兩個指標指向,待會再談。第4種情況是對稱的,就不說了。

下面來看第三種情況示例:


現在要插入的點是14,請看是否符合第3種情況的描述。6是不是深度最深的發生不平衡的點?14是不是插入在A的右孩子的左子樹?符合是吧,那就先順時針旋轉7和15,中間結果如下圖所示:

現在7是A的new child了是吧,那就逆時針旋轉6和7就可以了。


接著來分析singlerotateLL() 和doublerotateRL() 的實現,剩下兩個函式就是對稱的了。

首先是singlerotateLL(),看下圖:

改動其實很簡單,先把Y解綁當k2的左孩子,接著k2成為k1的右孩子,程式碼如下:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
/* return pointer to the new root */
static AVLNodePtr singlerotateLL(AVLNodePtr k2)
{
    AVLNodePtr 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;
    return k1;
}

接著是doublerotateRL()的實現,看下圖:


很明顯B或者C的插入使得k3(A)不平衡了,那麼首先應該是k1和k2逆時針旋轉,所以呼叫

k3->left = singlerotateRR(k3->left);

接著是k3和new child 順時針旋轉,呼叫singlerotateLL(k3);

so easy, 程式碼如下:

 C++ Code 
1
2
3
4
5
static AVLNodePtr doublerotateRL(AVLNodePtr k3)
{
    k3->left = singlerotateRR(k3->left);
    return singlerotateLL(k3);
}

完整的測試程式碼如下:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#include <stdio.h>
#include <stdlib.h>

struct AVLNode;
typedef struct AVLNode *AVLNodePtr;

struct AVLNode
{
    int element;
    AVLNodePtr left;
    AVLNodePtr right;
    int height;
};

void makeempty(AVLNodePtr T)
{
    if (T == NULL)
        return;
    else
    {
        makeempty(T->left);
        makeempty(T->right);
        free(T);
    }
}

static int height(AVLNodePtr p)
{
    if (p == NULL)
        return -1;
    else
        return p->height;
}

static int Max(int ln, int rn)
{
    return ln > rn ? ln : rn;
}

/* return pointer to the new root */
static AVLNodePtr singlerotateLL(AVLNodePtr k2)
{
    AVLNodePtr 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;
    return k1;
}

static AVLNodePtr singlerotateRR(AVLNodePtr k1)
{
    AVLNodePtr k2 = k1->right;
    k1->right = k2->left;
    k2->left = k1;
    k1->height = Max(height(k1->left), height(k1->right)) + 1;
    k2->height = Max(k1->height, height(k2->right)) + 1;
    return k2;
}

static AVLNodePtr doublerotateRL(AVLNodePtr k3)
{
    k3->left = singlerotateRR(k3->left);
    return singlerotateLL(k3);
}

static AVLNodePtr doublerotateLR(AVLNodePtr k3)
{
    k3->right = singlerotateLL(k3->right);
    return singlerotateRR(k3);
}


AVLNodePtr insert(int X, AVLNodePtr T)
{
    if (T == NULL)
    {
        /* create and return a one-node tree */
        T = (AVLNodePtr)malloc(sizeof(struct AVLNode));
        if (T == NULL)
        {
            printf("out of space!");
            exit(1);
        }
        else
        {
            T->element = X;
            T->height = 0;
            T->left = T->right = NULL;
        }
    }

    else if (X < T->element)
    {
        T->left = insert(X, T->left);
        if (height(T->left) - height(T->right) == 2)
        {
            if (X < T->left->element)
                T = singlerotateLL(T);
            else
                T = doublerotateRL(T);
        }
    }

    else if (X > T->element)
    {
        T->right = insert(X, T->right);
        if (height(T->right) - height(T->left) == 2)
        {
            if (X > T->right->element)
                T = singlerotateRR(T);
            else
                T = doublerotateLR(T);
        }
    }
    /* else X is in the tree already; we'll do nothing */
    T->height = Max(height(T->left), height(T->right)) + 1;
    return T;
}

void inorder(AVLNodePtr T)
{
    if (T == NULL)
        return;
    else
    {
        inorder(T->left);
        printf("%d ", T->element);
        inorder(T->right);
    }
}

int main(void)
{
    int arr[] = {32145671615141312111089};
    AVLNodePtr T = NULL;
    for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
        T = insert(arr[i], T);

    inorder(T);
    makeempty(T);
    return 0;
}

程式碼將陣列元素插入後,中序遍歷後輸出,即1~16的順序排列。

注意:輸入陣列元素就不要搞成有序的了,如果程式碼中沒有調整的實現,整個樹就是個右斜樹,但即使實現了調整,也會使得每插入一次就調整一次,何必內耗啊。很顯然,平衡二叉樹的優勢在於不會出現普通二叉查詢樹的最差情況。其查詢的時間複雜度為O(logN)。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

注:理解旋轉函式的實現關鍵在於理解實現時的限制:

The key to understanding how a rotation functions is to understand its constraints. In particular the order of the leaves of the tree (when read left to right for example) cannot change (another way to think of it is that the order that the leaves would be visited in a depth first search must be the same after the operation as before ).

Another constraint is the main property of a binary search tree, namely that the right child is greater than the parent and the left child is lesser than the parent.

Detailed illustration:


Using the terminology of Root for the parent node of the subtrees to rotate, Pivot for the node which will become the new parent node,RS for rotation side upon to rotate and OS for opposite side of rotation. In the above diagram for the root Q, the RS is C and the OS is P. The pseudo code for the rotation is:

Pivot = Root.OS
Root.OS = Pivot.RS
Pivot.RS = Root
Root = Pivot


參考:

《data structure and algorithm analysis in c》

《Data Structures》

相關文章