1、概念:
AVL樹本質上還是一個二叉搜索樹,不過比二叉搜索樹多了一個平衡條件:每個節點的左右子樹的高度差不大於1。
二叉樹的應用是為了彌補鏈表的查詢效率問題,但是極端情況下,二叉搜索樹會無限接近於鏈表,這種時候就無法體現二叉搜索樹在查詢時的高效率,而最初出現的解決方式就是AVL樹。如下圖:

2、旋轉
說到AVL樹就不得不提到樹的旋轉,旋轉是AVL維持平衡的方式,主要有以下四種類型。
2.1、左左旋轉
如圖2-1所示,此時A節點的左樹與右樹的高度差為2,不符合AVL的定義,此時以B節點為軸心,AB間連線為轉軸,將A節點旋轉至B節點下方,由B節點的父節點變成子節點。實現所有節點的左右子樹高
度差小於等於1。如圖2-2。

2.2、右右旋轉
右右旋轉與左左旋轉類似,但是動作相反,如圖2-3,2-4所示,以B節點為軸心,BC間連線為軸,將C節點旋轉至B下方,成為B的子節點。實現所有節點的左右子樹高度差小於等於1。

2.3、左右旋轉
左右旋轉稍復雜一點,需要旋轉兩次,如果用左左旋轉的方式直接旋轉圖2-5中的樹,會變成圖2-8的樣子,此時B節點的左右子樹高度差依然沒有平衡,所以要先對2-5的樹做一步處理,就是以BC為軸,
將C節點旋轉至B節點的位置,如圖2-6所示,此時就將樹轉換成了左左旋轉的場景,之后使用左左旋轉即可完成平衡。
2.4、右左旋轉
右左旋轉與左右旋轉類似,也是旋轉兩次。如下圖,具體細節請看下面的代碼詳解。

3、代碼實現
3.1、新增
二叉樹的插入不在贅述,這里只分析插入時的平衡方法。如果新增節點沒有兄弟節點時會引起樹的高度變化,此時需要對上層節點的平衡值進行修改,如果出現了不平衡樹,則需要調用平衡方法,代碼如下:
private void balance(Node node){ Node parent = node.getParent(); Node node_middle = node; Node node_prev = node; Boolean avl = true; do{ if(node_middle == parent.getLeft() && (-1 <= parent.getAVL()-1 && parent.getAVL()-1 <= 1)){ //node_middle為parent的左樹,此時parent左樹高度+1不會造成不平衡。 parent.subAVL(); node_prev = node_middle; node_middle = parent; //由於上面對parent的平衡值進行了修改,如果修改后的平衡值為0,說明此時parent節點的高度沒有改變,之前較短的左樹高度+1,變為與右樹高度相同。 if(parent != null && parent.getAVL() == 0) parent = null; else parent = parent.getParent(); }else if(node_middle == parent.getRight() && (-1 <= parent.getAVL()+1 && parent.getAVL()+1 <= 1)){ //node_middle為parent的右樹,此時parent右樹高度+1不會造成不平衡。 parent.addAVL(); node_prev = node_middle; node_middle = parent; //由於上面對parent的平衡值進行了修改,如果修改后的平衡值為0,說明此時parent節點的高度沒有改變,之前較短的右樹高度+1,變為與左樹高度相同。 if(parent != null && parent.getAVL() == 0) parent = null; else parent = parent.getParent(); }else{//出現最小不平衡節點,新增時不需要考慮更高節點,所以直接中斷循環,調用平衡方法 avl = false; } }while(parent != null && avl); if(parent == null){ return; } //選擇相應的旋轉方式 chooseCalculation(parent, node_middle, node_prev); }
3.2、刪除
刪除較新增復雜一些,主要是因為存在一次旋轉無法達到平衡效果的情況,而且刪除本身也分為三種情況,分別是:
3.2.1、刪除葉子節點
由於葉子節點沒有子樹,不涉及替換的問題,所以直接刪除即可,如果刪除節點沒有兄弟節點會引起高度變化,此時依次對父級節點的平衡值做對應修改,如果出現不平衡樹則要進行旋轉。
3.2.2、刪除節點存在一個子節點
此時將子節點上移,替換刪除節點的位置,這個操作勢必會造成所在子樹的高度變化,所以需要依次對父級節點的平衡值做對應修該,如果出現不平衡樹進行旋轉操作。
3.2.3、刪除節點存在兩個子節點
這種情況比較復雜,由於存在兩個子節點,所以不能簡單的將其中一個子節點提高一級,因此需要在節點的子樹中搜索一個合適的節點進行替換,之前自己構思的時候選擇的是搜尋一個最長子樹的最
接近刪除節點的值的葉子節點,具體實現的時候發現需要考慮的場景太多,並不適合,參考算法導論上的思路后改成了搜尋左樹的最大節點,此時只需考慮左樹的情況即可。實現方法如下:
public void deleteNode(int item){ Node node = get(item); if(node == null) return; Node parent = node.getParent(); if(!node.hasChild()){//葉子節點 if(parent == null){//刪除最后節點 root = null; return; } if(node.hasBrother()){//node有兄弟節點時,需要判斷是否需要調用平衡方法 if(node == parent.getLeft()) isBalance(node, 1); else isBalance(node, -1); parent.deleteChildNode(node); }else{//node沒有兄弟節點時,高度減一,需要進行平衡 deleteAvl(node); parent.deleteChildNode(node); } }else if(node.getLeft() != null && node.getRight() == null){//有一個子節點時,將子節點上移一位,然后進行平衡即可 if(parent == null){//刪除的是根節點 root = node; return; } if(node == parent.getLeft()){ parent.setLeft(node.getLeft()); }else{ parent.setRight(node.getLeft()); } node.getLeft().setParent(parent); deleteAvl(node.getLeft()); }else if(node.getLeft() == null && node.getRight() != null){//有一個子節點時,將子節點上移一位,然后進行平衡即可 if(parent == null){//刪除的是根節點 root = node; return; } if(node == parent.getRight()){ parent.setRight(node.getRight()); }else{ parent.setLeft(node.getRight()); } node.getRight().setParent(parent); deleteAvl(node.getRight()); } else{//有兩個子節點時,先在節點左樹尋找最大節點last,然后刪除last,最后將被刪除節點的value替換為last的value Node last = findLastNode(node); int tmp = last.getValue(); deleteNode(last.getValue()); node.setValue(tmp); } node = null;//GC }
3.3、旋轉
3.3.1、左左旋轉
private void LeftLeftRotate(Node node){ Node parent = node.getParent(); if(parent.getParent() != null && parent == parent.getParent().getLeft()){ node.setParent(parent.getParent()); parent.getParent().setLeft(node); }else if(parent.getParent() != null && parent == parent.getParent().getRight()){ node.setParent(parent.getParent()); parent.getParent().setRight(node); }else{ root = node; node.setParent(null); } parent.setParent(node); parent.setLeft(node.getRight()); if(node.getRight() != null) node.getRight().setParent(parent); node.setRight(parent); if(node.getAVL() == -1){//只有左節點時,parent轉換后沒有子節點 parent.setAVL(0); node.setAVL(0); }else if(node.getAVL() == 0){//node有兩個子節點,轉換后parent有一個左節點 parent.setAVL(-1); node.setAVL(1); }//node.getAVL()為1時會調用左右旋轉 }
3.3.2、右右旋轉
private void RightRightRotate(Node node){ Node parent = node.getParent(); if(parent.getParent() != null && parent == parent.getParent().getLeft()){ node.setParent(parent.getParent()); parent.getParent().setLeft(node); }else if(parent.getParent() != null && parent == parent.getParent().getRight()){ node.setParent(parent.getParent()); parent.getParent().setRight(node); }else{ root = node; node.setParent(null); } parent.setParent(node); parent.setRight(node.getLeft()); if(node.getLeft() != null) node.getLeft().setParent(parent); node.setLeft(parent); if(node.getAVL() == 1){ node.setAVL(0); parent.setAVL(0); }else if(node.getAVL() == 0){//當node有兩個節點時,轉換后層數不會更改,左樹比右樹高1層,parent的右樹比左樹高一層 parent.setAVL(1); node.setAVL(-1); } }
3.3.3、左右旋轉
private void LeftRightRotate(Node node){ Node parent = node.getParent(); Node child = node.getRight(); //左右旋轉時node的avl必為1,所以只需考慮child的avl if(!child.hasChild()){ node.setAVL(0); parent.setAVL(0); }else if(child.getAVL() == -1){ node.setAVL(0); parent.setAVL(1); }else if(child.getAVL() == 1){ node.setAVL(-1); parent.setAVL(0); }else if(child.getAVL() == 0){ node.setAVL(0); parent.setAVL(0); } child.setAVL(0); //第一次交換 parent.setLeft(child); node.setParent(child); node.setRight(child.getLeft()); if(child.getLeft() != null) child.getLeft().setParent(node); child.setLeft(node); child.setParent(parent); //第二次交換 if(parent.getParent() != null && parent == parent.getParent().getLeft()){ child.setParent(parent.getParent()); parent.getParent().setLeft(child); }else if(parent.getParent() != null && parent == parent.getParent().getRight()){ child.setParent(parent.getParent()); parent.getParent().setRight(child); }else{ root = child; child.setParent(null); } parent.setParent(child); parent.setLeft(child.getRight()); if(child.getRight() != null) child.getRight().setParent(parent); child.setRight(parent); }
3.3.4、右左旋轉
private void RightLeftRotate(Node node){ Node parent = node.getParent(); Node child = node.getLeft(); if(!child.hasChild()){ node.setAVL(0); parent.setAVL(0); }else if(child.getAVL() == -1){ node.setAVL(1); parent.setAVL(0); }else if(child.getAVL() == 1){ node.setAVL(0); parent.setAVL(-1); }else if(child.getAVL() == 0){ parent.setAVL(0); node.setAVL(0); } child.setAVL(0); //第一次交換 parent.setRight(child); node.setParent(child); node.setLeft(child.getRight()); if(child.getRight() != null) child.getRight().setParent(node); child.setRight(node); child.setParent(parent); //第二次交換 if(parent.getParent() != null && parent == parent.getParent().getLeft()){ child.setParent(parent.getParent()); parent.getParent().setLeft(child); }else if(parent.getParent() != null && parent == parent.getParent().getRight()){ child.setParent(parent.getParent()); parent.getParent().setRight(child); }else{ root = child; child.setParent(null); } parent.setParent(child); parent.setRight(child.getLeft()); if(child.getLeft() != null) child.getLeft().setParent(parent); child.setLeft(parent); }
完整代碼github地址:https://github.com/ziyuanjg/AVLTree