AVL樹的平衡算法(JAVA實現)


 
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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM