資料結構之 AVL樹(平衡二叉樹)(C語言實現)
AVL樹(平衡二叉樹)
1. AVL樹定義和性質
AVL(Adelson-Velskii和Landis發明者的首字母)樹時帶有平衡條件的二叉查詢樹。
二叉查詢樹的效能分析:
- 在一顆左右子樹高度平衡情況下,最優的時間複雜度為O(
log2n ),這與這半查詢相同; - 在一個只有右子樹的二叉樹中,最差的時間複雜度會脫變為線性查詢O(n)。
由於二叉查詢樹存在以上問題,所以AVL樹通過旋轉使自身始終保持平衡狀態,因此,一顆基於二叉查詢樹的AVL樹具有如下性質:
- 二叉查詢樹中任何一個結點的左子樹和右子樹高度相差的絕對值最多為1。
- 它的左、右子樹也分別都是平衡二叉樹。
如果往上圖左邊的AVL樹插入結點6,那麼該樹就會破壞結點8的平衡條件(如下圖),為了恢復AVL樹的性質,介紹修正的方法:旋轉(rotation)
2. 旋轉
當AVL樹的平衡被破壞時,我們將必須重新平衡的結點稱為α(剛才由於插入結點6而必須重新平衡的結點8)。這種不平衡出現在下面的四種情況:
- 對α的左兒子的左子樹進行一次插入。
- 對α的左兒子的右子樹進行一次插入。
- 對α的右兒子的左子樹進行一次插入。
- 對α的右兒子的右子樹進行一次插入。
上述四種情況,理論上只有兩種情況,但是程式設計角度還是四種情形。
- 情形1和4時關於α映象對稱(即左 - 左或右 - 右情況),因此該情況通過對樹進行一次單旋轉(single rotation)而完成調整;
- 情形2和3時關於α映象對稱(即左 - 右或右 - 左情況),因此該情形通過雙旋轉(double rotation)
2.1單旋轉
下圖顯示單旋轉如何調整情況1(左 - 左情況)。虛線表示樹的各層
如左圖所示:k2節點不滿足AVL的平衡條件,因為它的左子樹比右子樹深兩層。
為了恢復平衡,就必須將k1子樹整體上移作為根節點(因為k1子樹深),k2子樹整體下沉(因為k2子樹淺),成為k1的右孩子。同時必須滿足二叉查詢樹的左子樹必須小於右子樹的性質,將Y節點作為k2的左孩子。至此,單旋轉完成調整,X,Y,Z處於同一層。
將之前插入節點6而造成節點8不平衡的情況,通過單旋轉實現平衡(通過改變結點的孩子指標)。如下圖:
由於情形4(右 - 右)與情況1(左 - 左)時映象對稱的,因此,和理解上述單旋轉修復情況1的方法類似。
k1結點不滿足平衡條件。將k2結點上提,k1結點下沉,同時滿足左孩子小於右孩子的性質,即可完成修復情況4。
接下來,演示一個長一點的單旋轉的例子:
- 初始的空AVL樹插入關鍵字3、2和1,此時根節點已經被破壞平衡,屬於(左 - 左)情況,因此通過單旋轉調整。
- 繼續插入4和5,插入5之後,結點3又被破壞平衡,屬於(右 - 右)情況,通過單旋轉調整。
- 繼續插入6,根節點的平衡被破壞,屬於(右 - 右)情況,通古單旋轉調整。
- 繼續插入7,結點5平衡被破壞,屬於(右 - 右)情況,通過單旋轉調整。
2.2雙旋轉
單旋轉可以調整情況1和4,但是對於2和3,並不能修復AVL樹的性質(如下圖),k1的左右子樹的深度仍然為2。
2和3情況是(左 - 右)和(右 - 左)的情形,因此通過雙旋轉來修復AVL樹的性質。
情形2:對α的 左兒子的右子樹 進行一次插入。
情形3:對α的 右兒子的左子樹 進行一次插入。
通過雙旋轉修復情形2,如下圖所示:
如圖所示,第一次,先將k1作為根節點,以k1、k2和C為路徑,以(右 - 右)方式通過單旋轉調整,第二次,以k3為根節點,以k3、k2和k1為路徑,以(左 - 左)方式 通過單旋轉調整如右上圖所示。將最深的節點,提高到根節點,以達到平衡的性質。
因此,一次雙旋轉可以看成兩次單旋轉。
我們在剛才的例子上繼續插入節點,直觀的感受雙旋轉。繼續插入16和15結點,此時7的平衡被破壞。
此時的情形是(右 - 左)型別。通過一次雙旋轉,將最深的15旋轉到了被破壞平衡的節點位置。繼續插入14,6的平衡又被破壞。
此時屬於(右 - 左)型別。通過一次雙旋轉,將7旋轉到了被破壞平衡的節點位置。繼續插入13,導致根節點4平衡被破壞。
此時屬於(右 - 右)型別,通過一次單旋轉,將7節點旋轉到根節點,恢復平衡的性質。繼續插入12,節點15平衡被破壞。
此時屬於(左 - 左)型別。通過一側單旋轉。恢復平衡性質。繼續插入11,還需要進行一個單旋轉,對其後的10的插入也需要單旋轉,然後插入8不需要旋轉,這樣就建立了一顆近乎理想的平衡樹。
最後插入9,需要進行一次雙旋轉,就能得到下面的樹。
根據以上過程,就可以得到程式設計的細節:
- 插入:為了將關鍵字是X的新節點插入到一顆AVL樹中,遞迴的將X插入到T的相應的子樹中。如果子樹高度不變,則插入完成,如果在T中出現高度不平衡,那麼根據X和子樹中的關鍵字做適當的單旋轉或雙旋轉,跟新這些高度,從而完成插入。
- 刪除:刪除則要比插入更加複雜,遞迴尋找要刪除的關鍵字,分有一個或零個孩子和有兩個孩子的情況,分別去刪除,每次刪除完都要判斷是否平衡,不平衡,通過旋轉修正。
AVL樹的實現
- AVLtree.h檔案
#ifndef _SEARCHTREE_H_
#define _SEARCHTREE_H_
#define MAX(a, b) (a > b ? a : b)
#define GET_HEIGHT(T) (T == NULL ? -1 : T->height) //獲得樹的高度
typedef int myType;
typedef struct treeNode
{
myType element;
struct treeNode *lchild;
struct treeNode *rchild;
int height; //儲存樹的高度資訊
}AVLtree;
void preOrder(AVLtree *T);
void inOrder(AVLtree *T);
void postOrder(AVLtree *T);
void levelOrder(AVLtree *T);
AVLtree *find(myType data, AVLtree *T);
AVLtree *findMin(AVLtree *T);
AVLtree *findMax(AVLtree *T);
AVLtree *insert(myType data, AVLtree *T);
AVLtree *erase(myType data, AVLtree *T);
#endif
- AVLtree.c檔案
#include <stdio.h>
#include <stdlib.h>
#include "AVLtree.h"
#include "queue.h" //水平遍歷時需要佇列資料結構
AVLtree *find(myType data, AVLtree *T)
{
if(T == NULL)
return NULL;
if(data < T->element)
return find(data, T->lchild);
else if(data > T->element)
return find(data, T->rchild);
else
return T;
}
AVLtree *findMin(AVLtree *T)
{
if(T == NULL)
return NULL;
else if(T->lchild == NULL)
return T;
else
return findMin(T->lchild);
}
AVLtree *findMax(AVLtree *T)
{
if(T != NULL)
while(T->rchild != NULL)
T = T->rchild;
return T;
}
static int getHeight(AVLtree *T)
{
return GET_HEIGHT(T);
}
static AVLtree *singleRotateWithLeft(AVLtree *T)
{
AVLtree *TT;
TT = T->lchild;
T->lchild = TT->rchild;
TT->rchild = T;
T->height = MAX(getHeight(TT->lchild), getHeight(T->rchild)) + 1;
TT->height = MAX(getHeight(TT->lchild), T->height) + 1;
return TT;
}
static AVLtree *singleRotateWithRight(AVLtree *T)
{
AVLtree *TT;
TT = T->rchild;
T->rchild = TT->lchild;
TT->lchild = T;
T->height = MAX(getHeight(TT->lchild), getHeight(T->rchild)) + 1;
TT->height = MAX(getHeight(TT->lchild), T->height) + 1;
return TT;
}
static AVLtree *doubleRotateWithLeft(AVLtree *T)
{
T->lchild = singleRotateWithRight(T->lchild);
return singleRotateWithLeft(T);
}
static AVLtree *doubleRotateWithRight(AVLtree *T)
{
T->rchild = singleRotateWithLeft(T->rchild);
return singleRotateWithRight(T);
}
AVLtree *insert(myType data, AVLtree *T)
{
if(T == NULL) {
T = (AVLtree *)malloc(sizeof(struct treeNode));
T->element = data;
T->lchild = NULL;
T->rchild = NULL;
T->height = 0;
} else if (data < T->element) {
T->lchild = insert(data, T->lchild);
if(GET_HEIGHT(T->lchild) - GET_HEIGHT(T->rchild) == 2)
if(data < T->lchild->element)
T = singleRotateWithLeft(T);
else
T = doubleRotateWithLeft(T);
} else if (data > T->element) {
T->rchild = insert(data, T->rchild);
if(GET_HEIGHT(T->rchild) - GET_HEIGHT(T->lchild) == 2)
if(data > T->rchild->element)
T = singleRotateWithRight(T);
else
T = doubleRotateWithRight(T);
}
T->height = MAX(getHeight(T->lchild), getHeight(T->rchild) + 1);
return T;
}
AVLtree *erase(myType data, AVLtree *T)
{
AVLtree *tmpNode;
if(T == NULL) {
printf("NOT FOUNT\n");
} else if (data < T->element) {
T->lchild = erase(data, T->lchild);
if(getHeight(T->rchild) - getHeight(T->lchild) == 2) {
AVLtree *tmp = T->rchild;
if(getHeight(tmp->lchild) > getHeight(tmp->rchild))
T = doubleRotateWithRight(T);
else
T = singleRotateWithRight(T);
}
} else if (data > T->element) {
T->rchild = erase(data, T->rchild);
if(getHeight(T->lchild) - getHeight(T->rchild) == 2) {
AVLtree *tmp = T->lchild;
if(getHeight(tmp->rchild) > getHeight(tmp->lchild))
T = doubleRotateWithLeft(T);
else
T = singleRotateWithLeft(T);
}
//found element to delete, two children
} else if (T->lchild && T->rchild){
if(getHeight(T->rchild) > getHeight(T->lchild)) {
tmpNode = findMin(T->rchild);//將右子樹的最小值代替root
T->element = tmpNode->element;
T->rchild = erase(T->element, T->rchild);
} else {
tmpNode = findMax(T->lchild);//將左子樹的最大值代替root
T->element = tmpNode->element;
T->lchild = erase(T->element, T->lchild);
}
} else {
//one or zero children
tmpNode = T;
T = (T->lchild == NULL ? T->rchild : T->lchild);
free(tmpNode);
}
return T;
}
void levelOrder(AVLtree *T)
{
QUEUE *q = createQueue(100);
while(T != NULL) {
printf("%d ", T->element);
if(T->lchild != NULL)
enQueue(T->lchild, q);
if(T->rchild != NULL)
enQueue(T->rchild, q);
if(!isEmpty(q))
T = frontAndDequeue(q);
else
T = NULL;
}
disposeQueue(q);
}
void preOrder(AVLtree *T)
{
if(T != NULL) {
printf("%d ", T->element);
preOrder(T->lchild);
preOrder(T->rchild);
}
}
void inOrder(AVLtree *T)
{
if(T != NULL) {
inOrder(T->lchild);
printf("%d ", T->element);
inOrder(T->rchild);
}
}
void postOrder(AVLtree *T)
{
if(T != NULL) {
postOrder(T->lchild);
postOrder(T->rchild);
printf("%d ", T->element);
}
}
本文參考:《資料結構與演算法分析:C語言實現》——Mark Allen Weiss (維斯)