????本文在看圖說話之二叉排序樹的基礎(chǔ)上介紹了平衡二叉排序樹,結(jié)構(gòu)性較好的二叉排序樹其插入和刪除操作的時間復雜度接近Log(N),但是存在某些特定的情況二叉排序樹會退化到單鏈表的情況,比如由順序或者逆序或者基本有序的數(shù)組構(gòu)建的排序二叉樹,此時插入和刪除操就上升到了Log(N)的時間復雜度。下圖所示就是結(jié)構(gòu)性良好和退化的二叉排序樹。
????為了解決二叉排序樹的這種退化的情況,在其基礎(chǔ)上提出了平衡二叉排序樹,實現(xiàn)平衡二叉排序樹的方法有很多種,其中最基礎(chǔ)就是AVL樹,本文詳細的介紹下AVL樹實現(xiàn)平衡的基本原理以保證二叉排排序樹具有Log(N)的時間復雜界。
一丶平衡二叉排序樹
????平衡二叉排序樹如其名字在二叉樹上約定了平衡條件,通過下圖這個平衡二叉樹和非平衡二叉樹來說明平衡二叉樹的平衡條件。
????平衡性,是指每個節(jié)點的左子樹高度和右子樹高度之差不超過1,即 Math.abs(Hieght(node.left) –Height(node.right))<1。對于圖2中的平衡二叉樹而言,其上每個節(jié)點都滿足這個性質(zhì)。圖2中的非平衡樹,之所以不是平衡樹,是因為在根節(jié)點處平衡性遭到了破壞,其左子樹高度和右子樹高度之差為2。
二丶AVL樹平衡性調(diào)整策略
????我覺得研究AVL樹調(diào)整平衡性策略的正確姿勢應(yīng)該包括兩步,第一步研究導致二叉排序樹不平衡的情況是哪幾種。第二步針對具體的不平衡情況如何調(diào)整。
先研究二叉排序樹的不平衡情況,然后針對二叉排序樹的不平衡特點給出解決方案:
(1) 向節(jié)點的左兒子的左子樹插入元素導致的平衡性破壞。
????可以看到,插入元素1后,是節(jié)點10處的平衡性遭到了破壞。這里要強調(diào)一下,在更一般的情況下,插入元素有破壞平衡性的可能,而平衡性被破壞的節(jié)點可能發(fā)生在插入路徑上的每一個節(jié)點,可能是父節(jié)點,可能是其他節(jié)點。
對于該種平衡性破壞給出的解決方法稱為左旋轉(zhuǎn)其過程如下圖所示:
????通過圖4可以清晰的看出Single左旋轉(zhuǎn)調(diào)整平衡性的特點,利用文字描述過于拗口,利用偽代碼描述如下:假設(shè)平衡性被破壞的節(jié)點是node。
//調(diào)整node節(jié)點平衡性
node = SingleLeftRotation(node);
//balacne函數(shù)定義如下
public node SingleLeftRotation( (Nodenode){
if(node ==null) return null;
Node newRoot= node.left;
node.left = newRoot.right;
newRoot.right = node;
return newRoot;
}
(2)向節(jié)點的右兒子的右子樹插入元素導致的平衡性破壞。
該種情況下的非平衡性情況十分清晰,并且和第一種非平衡性情況是對稱的,不妨成這種非平衡性破壞為“右右“,第一種非平衡性破壞為”左左“。該種情況下的調(diào)整過程如圖所示,這調(diào)整過程稱為single右旋轉(zhuǎn)。
上述調(diào)整過程用偽代碼實現(xiàn)如下:
//調(diào)整node節(jié)點平衡性
node = SingleRightRotation(node);
//balacne函數(shù)定義如下
public node SingleRightRotation( (Nodenode){
if(node==null) return null;
Node newRoot= node.right;
node.right = newRoot.left;
newRoot.left= node;
return newRoot;
}
(3)向節(jié)點的左兒子的右子樹插入元素導致平衡性破壞
這種不平衡性的情況,形象的將其稱之為“左右“不平衡,該情況的不平衡的過程如下圖所示:
????圖8清晰的說明了調(diào)整平衡性的過程,在掌握的single左旋轉(zhuǎn)和single右旋轉(zhuǎn)的基礎(chǔ)上看懂上述過程絲毫不苦難,上述過程稱為double左旋轉(zhuǎn)利用偽代碼描述如下:
//調(diào)整node節(jié)點平衡性
node = dounleLeftRotation (node);
//balacne函數(shù)定義如下
public node dounleLeftRotation(Nodenode){
node.left=singleRightRotation(node.left);
node = singleLeftRotation(node);
return node;
}
(4)向節(jié)點的右兒子的左子樹插入元素導致平衡性破壞
該種平衡情況是向右兒子的左子樹插入元素導致不平衡,這種情況形象的稱之為“右左“不平衡,和”左右“不平衡是對稱的處理的手段都是類似的。下圖詳細的描述了這種情況如何調(diào)整平衡
通過圖10可以清晰的看到調(diào)整平衡的過程,上述過程和double左旋轉(zhuǎn)是對稱的操作,利用偽代碼描述如下:
//調(diào)整node節(jié)點平衡性
node = dounleRightRotation (node);
//balacne函數(shù)定義如下
public node dounleLeftRotation(Nodenode){
node.left=singleLeftRotation(node.left);
node = singleRightRotation(node);
return node;
}
三丶AVL樹完整的java實現(xiàn)
public classAVLTree {
publicNode root = null;
publicstatic void main(String []args){
int[] elements = new int[]{1,2,3,4,5};
AVLTree avl = new AVLTree();
/****測試插入,建樹和中序遍歷操作***/
avl.buildTree(elements);
/********測試二叉樹的高度***************/
System.out.println(avl.root.height);
// bst.midTrace();
// System.out.println();
/****測數(shù)刪除操作之無兒子***************/
// bst.delete(9);
// bst.midTrace();
/****測數(shù)刪除操作之只有一個兒子***************/
// bst.delete(4);
// bst.midTrace();
/****測數(shù)刪除操作只有兩個兒子***************/
// bst.delete(5);
// bst.midTrace();
}
//節(jié)點類型定義
publicstatic class Node{
publicint element;
publicNode left;
publicNode right;
//平衡二叉排序樹,為了保證平衡,對每個節(jié)點維護了高度信息
publicint height;
publicNode(int element){
this.element = element;
left = null;
right = null;
height = 0;
}
}
//外部使用的插入方法
publicvoid insert(int element){
root=insert(element,root);
}
//內(nèi)部使用的插入方法
private Node insert(int element,Node root){
if(root==null)
return new Node(element);
intdelta = element-root.element;
if(delta<0){
root.left = insert(element,root.left);
}elseif(delta>0){
root.right = insert(element,root.right);
}else{
//不做處理
}
returnbalanced(root);
}
//外部使用的建樹方法
publicvoid buildTree(int [] elements){
if(root==null){
for(int i=0;i<elements.length;i++){
insert(elements[i]);
}
}else{
//若樹以存在,則不能建
}
}
publicvoid delete(int element){
delete(element,root);
}
publicNode delete(int element,Node root){
//未定位到刪除元素
if(root==null) return null;
intdelta = element-root.element;
if(delta<0){
root.left = delete(element,root.left);
}elseif(delta>0){
root.right =delete(element,root.right);
}else{
if(root.left!=null&&root.right!=null){
Node node = findMin(root.right);
root.element =node.element;
root.right = delete(node.element,root.right);
}else{
root = root.left!=null?root.left:root.right;
}
}
returnbalanced(root);
}
//中序遍歷二叉樹
publicvoid midTrace(){
if(root!=null){
print(root);
}
}
privatevoid print(Node node){
if(node!=null){
print(node.left);
System.out.print(node.element+" ");
print(node.right);
}
}
//找到該節(jié)點下子樹的最小節(jié)點。
publicNode findMin(Node node){
if(node.left==null)
return node;
else
return findMin(node.left);
}
publicNode balanced(Node node){
node.height= Math.max(getHeight(node.left),getHeight(node.right))+1;
if(getHeight(node.left)-getHeight(node.right)>1){
if(getHeight(node.left.left)>getHeight(node.left.right)){
node = singleLeftRotation(node);
}else{
node = doubleLeftRotation(node);
}
}
if(getHeight(node.right)-getHeight(node.left)>1){
if(getHeight(node.right.left)>getHeight(node.right.right)){
node =doubleLeftRotation(node);
}else{
node = singleRightRotation(node);
}
}
returnnode;
}
publicint getHeight(Node node){
returnnode==null?-1:node.height;
}
publicNode singleLeftRotation(Node node){
Node newRoot = node.left;
node.left = newRoot.right;
//左右子樹變化更新高度
node.height = Math.max(getHeight(node.left),getHeight(node.right))+1;
newRoot.right=node;
//左右子樹變化更新高度
newRoot.height= Math.max(getHeight(newRoot.left),getHeight(newRoot.right))+1;
returnnewRoot;
}
publicNode singleRightRotation(Node node){
Node newRoot = node.right;
node.right = newRoot.left;
//左右子樹變化更新高度
node.height = Math.max(getHeight(node.left),getHeight(node.right))+1;
newRoot.left=node;
//左右子樹變化更新高度
newRoot.height= Math.max(getHeight(newRoot.left),getHeight(newRoot.right))+1;
returnnewRoot;
}
publicNode doubelRightRotation(Node node){
node.right = singleLeftRotation(node.right);
returnsingleRightRotation(node);
}
publicNode doubleLeftRotation(Node node){
node.left = singleRightRotation(node.left);
returnsingleLeftRotation(node);
}
}
Reference:
[1] 數(shù)據(jù)結(jié)構(gòu)與算法 java語言描述
[2] 原文博客鏈接