平衡二叉樹AVL
1 定義
平衡二叉樹是一種二叉排序樹,其中每個節點的左子樹和右子樹的高度差至多等於1。其中,二叉樹節點的左子樹深度減去右子樹深度的值稱為平衡因子(BF),因此平衡二叉樹節點的平衡因子值只能為1、0或-1。距離插入節點最近的,且平衡因子的絕對值大於1的節點為根的子樹,我們稱為最小不平衡子樹。
2 平衡二叉樹的構建過程
平衡二叉樹的構建過程基於二叉排序樹的構建過程,在插入節點的過程中,一旦出現不平衡現象(即某節點的平衡因子大於1或小於-1),就找出最小不平衡子樹,進行“旋轉”操作,調整最小不平衡子樹中各節點的連結關係,使之成為新的平衡子樹。“旋轉過程”就好比一條扁擔出現一頭重一頭輕的現象,若能將扁擔的支撐點改變,扁擔兩頭就平衡了。
前人總結出在二叉排序樹中插入節點而失去平衡的情況下,對最小不平衡子樹進行調整,有4種“旋轉”型別(LL型,RR型,LR型,RL型),分別對應不同的不平衡情況。其中LL型和RR型只需要一次旋轉,而LR型和RL型則需要兩次旋轉。每種型別又可進一步細分為3種情況,總共4×3=12種情況
LL型(最小不平衡子樹的根結點平衡因子值大於1且與根結點左孩子節點的平衡因子符號相同)
RR型(最小不平衡子樹的根結點平衡因子值小於-1且與根結點右孩子節點的平衡因子符號相同)
LR型(最小不平衡子樹的根結點平衡因子值大於1且與根結點左孩子節點的平衡因子符號不相同)
RL型(最小不平衡子樹的根結點平衡因子值小於-1且與根結點右孩子節點的平衡因子符號不相同)
注意最小不平衡子樹中節點的平衡因子值在旋轉調整前後的變化,其中LL型和RR型平衡因子變化一致,而LR型和RL型則需分開討論
3 程式碼
首先建立二叉樹節點進行定義
function Node(key){
this .data = key; //儲存資料
this.bf = 0; //平衡因子
this.lChild = null; //左孩子節點
this.rChild = null; //右孩子節點
}
接著是對不平衡子樹中某一節點T的一次左旋和右旋操作函式程式碼,返回旋轉後的新節點
//左旋
function L_Rotate(T){
let R = T.rChild;
T.rChild = R.lChild;
R.lChild = T;
return R;
}
//右旋
function R_Rotate(T){
let L = T.lChild;
T.lChild = L.rChild;
L.rChild = T;
return L;
}
現在我們假設對根結點為T的不平衡子樹做左旋和右旋操作函式分別為LeftBalance(T)
和RightBalance(T)
,該函式返回新的根結點。那麼平衡二叉樹的插入操作程式碼如下
var taller = true;
var temp = null;
function insertAVL(T,key){ //插入節點至平衡二叉樹
if (!T){ //節點不存在,則在此位置新建節點
T = new Node(key);
taller = true;
}else{
if (key === T.data){ //如果二叉樹中已有與key相同的值,而不插入
taller = false;
return 0;
}else if (key < T.data){ //如果插入值小於當前節點值
temp = insertAVL(T.lChild,key); //則在當前節點的左子樹繼續搜尋
if ( temp === 0) return 0; //未插入
T.lChild = temp;
if (taller){
switch (T.bf){ //在當前節點的左子樹成功插入節點時
case 1: //原本左子樹比右子樹高,插入節點後,需要做平衡處理
T = leftBalance(T);
taller = false;
break;
case 0: //原本左子樹和右子樹一樣高,插入節點後,樹長高
T.bf = 1;
taller = true;
break;
case -1: //原本左子樹比右子樹矮,插入節點後,現左右樹等高
T.bf = 0;
taller = false;
break;
}
}
}else{ //如果插入值大於當前節點值
temp = insertAVL(T.rChild,key); //則在當前節點的右子樹繼續搜尋
if ( temp === 0) return 0; //未插入
T.rChild = temp;
if (taller){
switch (T.bf){ //在當前節點的右子樹成功插入節點時
case 1: //原本左子樹比右子樹高,插入節點後,現左右樹等高
T.bf = 0;
taller = false;
break;
case 0: //原本左子樹和右子樹一樣高,插入節點後,樹長高
T.bf = -1;
taller = true;
break;
case -1: //原本左子樹比右子樹矮,插入節點後,需要做平衡處理
T = rightBalance(T);
taller = false;
break;
}
}
}
}
return T;
}
最後一步,實現對根結點為T的不平衡子樹的旋轉操作,需要分情況討論只用一次旋轉還是要有兩次旋轉。
function leftBalance(T){ //對根結點為T的不平衡子樹進行旋轉調整操作
let lc = T.lChild; //根結點的左孩子
switch (lc.bf){
case 1: //新節點插入在根結點左孩子的左子樹。為LL型
lc.bf = 0;
T.bf = 0;
return R_Rotate(T); //LL型為一次旋轉
case -1: //新節點插入在根結點左孩子的右子樹。為LR型
let rd = lc.rChild; //指向根結點左孩子的右子樹根
switch (rd.bf){ //對應LR型的三種平衡因子變化情況
case 1:
lc.bf = 0;
T.bf = -1;
break;
case -1:
lc.bf = 1;
T.bf = 0;
break;
case 0:
lc.bf = 0;
T.bf = 0;
break;
}
rd.bf = 0;
T.lChild = L_Rotate(T.lChild); //LR型為兩次旋轉
return R_Rotate(T);
}
}
function rightBalance(T){ //對根結點為T的不平衡子樹進行旋轉調整操作
let rc = T.rChild; //根結點的右孩子
switch (rc.bf){
case -1: //新節點插入在根結點左孩子的左子樹。為RR型
rc.bf = 0;
T.bf = 0;
return L_Rotate(T); //RR型為一次旋轉
case 1: //新節點插入在根結點左孩子的右子樹。為RL型
let ld = rc.lChild; //指向根結點左孩子的右子樹根
switch (ld.bf){ //對應RL型的三種平衡因子變化情況
case 1:
rc.bf = -1;
T.bf = 0;
break;
case -1:
rc.bf = 0;
T.bf = 1;
break;
case 0:
rc.bf = 0;
T.bf = 0;
break;
}
ld.bf = 0;
T.rChild = R_Rotate(T.rChild); //RL型為兩次旋轉
return L_Rotate(T);
}
}
平衡二叉樹的刪除操作更為複雜,先佔個坑,以後有空再來慢慢研究,https://blog.csdn.net/goodluckwhh/article/details/11786079/