對於一棵普通的二叉查找樹而言,在進行多次的插入或刪除後,容易讓樹失去平衡,導致樹的深度不是O(logN),而接近O(N),這樣將大大減少對樹的查找效率。一種解決辦法就是要有一個稱為平衡的附加的結構條件:任何節點的深度均不得過深。有一種最古老的平衡查找樹,即AVL樹。
AVL樹是帶有平衡條件的二叉查找樹。平衡條件是每個節點的左子樹和右子樹的高度最多差1的二叉查找樹(空樹的高度定義為-1)。相比於普通的二叉樹,AVL樹的節點需要增加一個變量保存節點高度。AVL樹的節點聲明如下:
typedef struct TreeNode *AvlTree; typedef struct TreeNode *Position; struct TreeNode { int Data; AvlTree Left; AvlTree Right; int Height; //保存節點高度 };
只有一個節點的樹顯然是AVL樹,之後我們向其插入節點。然而在插入過程中可能破壞AVL樹的特性,因此我們需要對樹進行簡單的修正,即AVL樹的旋轉。
設a節點在插入下一個節點後會失去平衡,這種插入可能出現四種情況:
1. 對a的左兒子的左子樹進行一次插入。(左-左)
2. 對a的左兒子的右子樹進行一次插入。(左-右)
3. 對a的右兒子的左子樹進行一次插入。(右-左)
4. 對a的右兒子的右子樹進行一次插入。(右-右)
情形1和4,情形2和3分別是關於A節點的鏡像對稱,故在理論上是兩種情況,而編程具體實現還是需要考慮四種。
單旋轉--情形1和4:
雙旋轉--情形2和3:
情形2和3就是向上圖中的子樹Y插入一個節點,由上圖可知,無論是左單旋還是右單旋都無法改變子樹Y的高度。解決辦法是再將子樹Y分解成根節點和相應的左子樹和右子樹,然後對相應的節點做相應的旋轉,如下圖:
下面一個題即是考察AVL樹的旋轉:題目來源:http://www.patest.cn/contests/mooc-ds/04-%E6%A0%914
An AVL tree is a self-balancing binary search tree. In an AVL tree, the heights of the two child subtrees of any node differ by at most one; if at any time they differ by more than one, rebalancing is done to restore this property. Figures 1-4 illustrate the rotation rules.
Now given a sequence of insertions, you are supposed to tell the root of the resulting AVL tree.
Input Specification:
Each input file contains one test case. For each case, the first line contains a positive integer N (<=20) which is the total number of keys to be inserted. Then N distinct integer keys are given in the next line. All the numbers in a line are separated by a space.
Output Specification:
For each test case, print ythe root of the resulting AVL tree in one line.
Sample Input 1:
5 88 70 61 96 120
Sample Output 1:
70
Sample Input 2:
7 88 70 61 96 120 90 65
Sample Output 2:
88
題目大意是先輸入一個整數N,然後依次輸入N個節點的值,以此建立AVL樹,最後輸出AVL樹的根節點的值。
代碼如下:
#include <cstdio> #include <cstdlib> typedef struct TreeNode *AvlTree; typedef struct TreeNode *Position; struct TreeNode { int Data; AvlTree Left; AvlTree Right; int Height; }; AvlTree Insert(int x, AvlTree T); //插入新節點,必要時調整 Position SingleRotateWithLeft(Position a); //左單旋 Position SingleRotateWithRight(Position b); //右單旋 Position DoubleRotateWithLeft(Position a); //左右旋 Position DoubleRotateWithRight(Position b); //右左旋 int Max(int x1, int x2); //返回兩個int中較大的 int Height(Position P); //返回一個節點的高度 int main() { int n, x; AvlTree T = NULL; scanf("%d", &n); for (int i = 0; i < n; i++) { scanf("%d", &x); T = Insert(x, T); } printf("%d\n", T->Data); //打印根節點的值 return 0; } AvlTree Insert(int x, AvlTree T) { if (T == NULL) { T = (AvlTree)malloc(sizeof(struct TreeNode)); T->Data = x; T->Left = T->Right = NULL; T->Height = 0; } else if (x < T->Data) //向左子樹插入 { T->Left = Insert(x, T->Left); if (Height(T->Left) - Height(T->Right) == 2) //需調整 { if (x < T->Left->Data) T = SingleRotateWithLeft(T); else T = DoubleRotateWithLeft(T); } } else if (x > T->Data) //向右子樹插入 { T->Right = Insert(x, T->Right); if (Height(T->Right) - Height(T->Left) == 2) //需調整 { if (x > T->Right->Data) T = SingleRotateWithRight(T); else T = DoubleRotateWithRight(T); } } /*else值為x的節點已經存在樹中,無需插入*/ /*更新節點高度*/ T->Height = Max(Height(T->Left), Height(T->Right)) + 1; return T; } Position SingleRotateWithLeft(Position a) { Position b = a->Left; a->Left = b->Right; b->Right = a; //更新a, b節點高度 a->Height = Max(Height(a->Left), Height(a->Right)) + 1; b->Height = Max(Height(b->Left), Height(b->Right)) + 1; return b; /*新的根節點*/ } Position SingleRotateWithRight(Position b) { Position a = b->Right; b->Right = a->Left; a->Left = b; //更新a,b節點高度 a->Height = Max(Height(a->Left), Height(a->Right)) + 1; b->Height = Max(Height(b->Left), Height(b->Right)) + 1; return a; /*新的根節點*/ } Position DoubleRotateWithLeft(Position a) { a->Left = SingleRotateWithRight(a->Left); return SingleRotateWithLeft(a); } Position DoubleRotateWithRight(Position b) { b->Right = SingleRotateWithLeft(b->Right); return SingleRotateWithRight(b); } int Max(int x1, int x2) { return (x1 > x2) ? x1 : x2; } int Height(Position P) { if (P == NULL) //空節點高度為-1 return -1; return P->Height; }
需要注意的細節是我們需要快速得到一個節點(包括空節點)的高度,所以我們需要些一個函數來處理空節點(空指針)的情況,而不是簡單的Position->Height。