前提
我們之前的二叉排序樹的插入(構建)是按照我們輸入的數據來進行的,若是我們的數據分布不同,那么就會構造不同的二叉樹
{ 62, 88, 58, 47, 35, 73, 51, 99, 37, 93 }

{ 35, 37, 47, 51, 58, 62, 73, 88, 93, 99 }

我們發現若是數組元素分布大小按順序,那么我們極有可能得到一顆極不平衡的二叉樹,而二叉樹深度越大,查找的次數越多,其查找時間復雜度可以高達O(n),那么如何構造一顆平衡的二叉樹?
平衡二叉樹
一:定義
平衡:
左右均勻
平衡因子:
將二叉樹上結點的左子樹深度減去右子樹深度的值稱為平衡因子BF(Balance Factor) BF=hl-hr
平衡二叉樹(AVL樹):
是一種二叉排序樹
空樹或任一結點左右子樹高度差的絕對值不超過1,即|BF|<=1

最小不平衡子樹
距離插入結點最近的,且平衡因子絕對值大於1的結點為根 的子樹,我們稱為最小不平衡子樹

二:平衡二叉樹實現原理
基本思想
在構建二叉排序樹的過程中,每當插入一個結點時,先檢查是否因插入而破壞了樹的平衡性,若是,則找出最小不平衡子樹。在保存二叉排序樹的前提下,調整最小不平衡子樹中各個結點之間的鏈接更新,進行相應的旋轉,使之成為新的平衡子樹
二叉排序樹構建過程
{3,2,1,4,5,6,10,9,8,7}
我們若是按照二叉排序樹進行構建 圖一

雖然會符合二叉排序樹的定義,但是高度達到8的二叉樹,查找不好,效率不高,我們應該盡可能是二叉排序樹保持平衡,比如圖二

開始構建平衡二叉樹AVL
1.選取第一個數據元素3,按照二叉排序樹方法正常構建數據,樹的平衡因子為0,符合平衡
2.選取第二個數據元素2,按照二叉排序樹方法正常構建數據,樹的平衡因子為1,符合平衡
3.選取第三個數據元素1,按照二叉排序樹方法構建數據位置,樹的根節點平衡因子為2,不符合平衡要求,我們找到最小不平衡子樹,進行旋轉

注意:平衡因子為正數,則右轉,為負數,則左轉
4.選取第四個數據元素4,按照二叉排序樹方法正常構建數據,樹的平衡因子沒改變,符合平衡

5.選取第五個數據元素5,按照二叉排序樹方法正常構建數據,結點3的BF變為-2,說明要進行旋轉,我們找到最小不平衡子樹,進行旋轉

負數,左旋
6.選取第六個數據元素6,按照二叉排序樹方法正常構建數據,發現結點2的BF變為-2,說明要進行旋轉,而且是左旋

注意:此時本來結點3是結點4的左孩子,由於旋轉后,需要滿足二叉排序樹圖像,因此我們將他變為結點2的右孩子
7.選取第七個數據元素7,按照二叉排序樹方法正常構建數據,發現結點5的BF變為-2,所以需要對這個最小不平衡子樹進行左旋

8.選取第八個數據元素10,按照二叉排序樹方法正常構建數據,樹的平衡因子沒改變,符合平衡

9.選取第八個數據元素9,按照二叉排序樹方法正常構建數據,發現結點7的BF值為-2,我們需要進行旋轉

注意:因為我們的結點7的BF=-2,而他的子結點10的BF是1,對於兩個符號不統一的最小不平衡子樹,
我們都應該先讓其符號相同,所以先對我們的最小不平衡子樹的子樹結點10和結點9安裝其結點10的BF=1,正數,先進行兩個結點的右旋,

然后再對整個不平衡子樹按照結點7的BF=-2進行左旋

10.選取第九個數據元素8,按照二叉排序樹方法正常構建數據,發現結點6的BF=-2,而且最小不平衡子樹的符號不統一

我們先對最小不平衡子樹的子樹進行旋轉,使得其符號統一,按照結點9的BF=1,進行右旋

使最小不平衡子樹符號相同,然后我們根據結點6的BF=-2,進行左旋

最后將所有的數據排序完成!!!
三:平衡二叉樹的難點
1.我們需要知道每個結點的BF值,應該從哪得知?
所以我們要在結構體中加入平衡因子數據域
typedef struct _BiTNode { ElemType data; int bf; struct _BiTNode* lchild, *rchild; }BiTNode,*BiTree;
2.我們如何動態修改每個結點的BF值?
(1)我們需要知道,我們插入一個結點,只會影響到該結點到根節點的路徑上的結點的BF值,是不會影響到其他結點的BF值





(2)我們插入一個新的結點,那么這個新的結點的BF值一定是0(可以看上圖)
(3)我們對一個最小不平衡子樹做了平衡處理后,會發現我們只對這個最小不平衡子樹的BF進行了改變,而對於這棵樹中的其他結點的BF值,雖然變換當中會改變,但是變化后和原來是一樣的。
下面我們對添加新結點前,添加后,樹平衡調整后的BF值進行觀察


這里不在最小平衡子樹中的點有0,1結點,開始和結束后其BF都沒有變化


這里不在最小平衡子樹中的點有1結點,開始和結束后其BF都沒有變化


這里不在最小平衡子樹中的點有1,2,3,4結點,開始和結束后其BF都沒有變化


這里不在最小平衡子樹中的點有1,2,3,4,5,6結點,開始和結束后其BF都沒有變化
總之:我們在考慮樹的結點的BF值時,我們只需要考慮我們的最小不平衡子樹的結點的BF值即可。
(4)同3注意:我們還發現,除了 參與旋轉的三個結點,在最小不平衡子樹的其他結點的BF值也不會改變
LL型

LR型

RR和RL型相同
所以:我們只需要考慮的結點是最小不平衡子樹的3個旋轉結點即可
(5)通過上面分析:我們只需要考慮3個結點的BF值變化即可,但是具體變化方式是不是有規律的?
LL型
插入結點時的變化,我們應該將BL的BF值修改,原來是0,插入子節點后變為1

做了平衡旋轉后,我們應該將最小不平衡子樹的根節點A和左子樹根節點B變為0

LR型(我們這里只考慮插入在雙親結點左側:分多種情況,要根據第三個結點再次進行分析)

首先是T指向新插入的C結點的雙親BR由原來的0變為1,再向上走T等於其雙親,原來也是0,但是這里是右轉所以由0變為-1,之后轉到A結點,發現是左轉,原來BF值是1,直接進入左旋轉平衡

左旋轉平衡,根據LR判斷,若是為1,我們將最小不平衡子樹根T置為-1,L和LR結點設為0



分為這三種情況(想吐)....RR和RL同上面分析。
四:代碼實現
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 typedef int Status; typedef int ElemType; typedef struct _BiTNode { ElemType data; int bf; struct _BiTNode* lchild, *rchild; }BiTNode,*BiTree; //右旋操作 /* 對以p為根節點的二叉排序樹進行右旋操作 處理之后p指向新的樹根結點,即旋轉處理之前的左子樹的根節點 */ void R_Rotate(BiTree *p) { BiTree L; L = (*p)->lchild; (*p)->lchild = L->rchild; L->rchild = (*p); *p = L; } //左旋操作 /* 對以p為根節點的二叉排序樹進行左旋操作 處理之后p指向新的樹根結點,即旋轉處理之前的右子樹的根節點 */ void L_Rotate(BiTree *p) { BiTree R; R = (*p)->rchild; (*p)->rchild = R->lchild; R->lchild = (*p); *p = R; } #define LH +1 //左高 #define EH 0 //等高 #define RH -1 //右高 //左平衡旋轉處理代碼 //其中傳入的T都是最小不平衡子樹的根節點 //我們在旋轉時主要關注的BF值就是最小不平衡子樹的根節點BF和根節點下面的子節點BF值 void LeftBalance(BiTree *T) //左平衡。我們主要考慮LL,LR兩種 { BiTree L,Lr; L = (*T)->lchild; switch (L->bf) //由於已經是要做平衡處理,所以L->bf不會出現EH狀態 { case LH: //LL直接右旋即可,注意LL后的結點平衡后都是0 (*T)->bf = L->bf = EH; //所有的BF跳轉都是基於旋轉之前的提前調整,方便些 R_Rotate(T); break; case RH: //LR旋轉,我們需要注意先要左旋,然后右旋 //在左右旋之前,我們要修改BF值 Lr = L->rchild; //Lr指向T的左孩子的右子樹 switch (Lr->bf) { /* 修改T及其左孩子的平衡因子 */ case LH: (*T)->bf = RH; L->bf = EH; break; case EH: (*T)->bf = EH; L->bf = EH; break; case RH: (*T)->bf = EH; L->bf = LH; break; } Lr->bf = EH; L_Rotate(&(*T)->lchild);/* 對T的左子樹作左旋平衡處理 */ R_Rotate(T); /* 對T作右旋平衡處理 */ break; } } /* 對以指針T所指結點為根的二叉樹作右平衡旋轉處理, */ void RightBalance(BiTree* T) { BiTree R, Rl; R = (*T)->rchild; /* R指向T的右子樹根結點 */ switch (R->bf) {/* 檢查T的右子樹的平衡度,並作相應平衡處理 */ case RH: /* 新結點插入在T的右孩子的右子樹上,要作單左旋處理 */ (*T)->bf = R->bf = EH; L_Rotate(T); break; case LH:/* 新結點插入在T的右孩子的左子樹上,要作雙旋處理 */ Rl = R->lchild;/* Rl指向T的右孩子的左子樹根 */ switch (Rl->bf) { case RH: (*T)->bf = LH; R->bf = EH; break; case EH: (*T)->bf = EH; R->bf = EH; break; case LH: (*T)->bf = EH; R->bf = RH; break; } Rl->bf = EH; R_Rotate(&(*T)->rchild);/* 對T的右子樹作右旋平衡處理 */ L_Rotate(T); break; } } /* 若在平衡的二叉排序樹T中不存在和e有相同關鍵字的結點,則插入一個 */ /* 數據元素為e的新結點,並返回1,否則返回0。若因插入而使二叉排序樹 */ /* 失去平衡,則作平衡旋轉處理,布爾變量taller反映T長高與否。 */ Status InsertAVL(BiTree* T, int e, Status *taller) { if (!*T) { //插入新結點 *T = (BiTree)malloc(sizeof(BiTNode)); (*T)->data = e; (*T)->lchild = (*T)->rchild = NULL; (*T)->bf = EH; *taller = TRUE; } else { if (e==(*T)->data) { //樹中已經存在和e有相同的關鍵字的結點則不再插入 *taller = FALSE; return FALSE; } else if (e<(*T)->data) { //應該繼續在T的左子樹中進行搜索 if (!InsertAVL(&(*T)->lchild, e, taller)) //未插入 return FALSE; if (*taller) //已插入到T的左子樹中,且左子樹長高 { switch ((*T)->bf) //檢測T樹的平衡度 { case LH: //原來是其父節點T的BF值為1,現在插入左孩子,其BF值變為2,直接進行左平衡處理 LeftBalance(T); *taller = FALSE; break; case EH: //原來左右子樹等高,現因左子樹增高而樹增高 (*T)->bf = LH; *taller = TRUE; break; case RH: //原來右子樹比左子樹高,現在左右等高 (*T)->bf = EH; *taller = FALSE; break; } } } else { //去右子樹搜索 if (!InsertAVL(&(*T)->rchild, e, taller)) //未插入 return FALSE; if (*taller) //已插入到T的左子樹中,且左子樹長高 { switch ((*T)->bf) //檢測T樹的平衡度 { case LH: //原來左子樹比右子樹高,現在左右等高 (*T)->bf = EH; *taller = FALSE; break; case EH: //原來左右子樹等高,現因右子樹增高而樹增高 (*T)->bf = RH; *taller = TRUE; break; case RH: //原來右子樹比左子樹高,現在高了兩個度,BF=2,需要進行右平衡旋轉 RightBalance(T); *taller = FALSE; break; } } } } return TRUE; } int main() { int i; int a[10] = { 3, 2, 1, 4, 5, 6, 7, 10, 9, 8 }; BiTree T = NULL; Status taller; for (i = 0; i < 10;i++) { InsertAVL(&T, a[i], &taller); } system("pause"); return 0; }

五:反思
不太熟練,需要多聯系,等我把這些都復習一遍,在做題的時候會進行更多的查漏補缺,而且上面缺少刪除部分代碼,在我真正理解后,會補上
