1. 程式人生 > >平衡二叉樹AVL

平衡二叉樹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/