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

 
一、概念:
  AVL樹本質上仍是一個二叉搜索樹,不過比二叉搜索樹多了一個平衡條件:每一個節點的左右子樹的高度差不大於1。
二叉樹的應用是爲了彌補鏈表的查詢效率問題,可是極端狀況下,二叉搜索樹會無限接近於鏈表,這種時候就沒法體現二叉搜索樹在查詢時的高效率,而最初出現的解決方式就是AVL樹。以下圖:
二、旋轉
  說到AVL樹就不得不提到樹的旋轉,旋轉是AVL維持平衡的方式,主要有如下四種類型。
  2.一、左左旋轉
    如圖2-1所示,此時A節點的左樹與右樹的高度差爲2,不符合AVL的定義,此時以B節點爲軸心,AB間連線爲轉軸,將A節點旋轉至B節點下方,由B節點的父節點變成子節點。實現全部節點的左右子樹高
  度差小於等於1。如圖2-2。
    

 

  2.二、右右旋轉
    右右旋轉與左左旋轉相似,可是動做相反,如圖2-3,2-4所示,以B節點爲軸心,BC間連線爲軸,將C節點旋轉至B下方,成爲B的子節點。實現全部節點的左右子樹高度差小於等於1。  
    2.三、左右旋轉
      左右旋轉稍複雜一點,須要旋轉兩次,若是用左左旋轉的方式直接旋轉圖2-5中的樹,會變成圖2-8的樣子,此時B節點的左右子樹高度差依然沒有平衡,因此要先對2-5的樹作一步處理,就是以BC爲軸,
    將C節點旋轉至B節點的位置,如圖2-6所示,此時就將樹轉換成了左左旋轉的場景,以後使用左左旋轉便可完成平衡。

  

    2.四、右左旋轉
      右左旋轉與左右旋轉相似,也是旋轉兩次。以下圖,具體細節請看下面的代碼詳解。
三、代碼實現
  3.一、新增
    二叉樹的插入不在贅述,這裏只分析插入時的平衡方法。若是新增節點沒有兄弟節點時會引發樹的高度變化,此時須要對上層節點的平衡值進行修改,若是出現了不平衡樹,則須要調用平衡方法,代碼以下:
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.二、刪除node

    刪除較新增複雜一些,主要是由於存在一次旋轉沒法達到平衡效果的狀況,並且刪除自己也分爲三種狀況,分別是:
    3.2.一、刪除葉子節點
      因爲葉子節點沒有子樹,不涉及替換的問題,因此直接刪除便可,若是刪除節點沒有兄弟節點會引發高度變化,此時依次對父級節點的平衡值作對應修改,若是出現不平衡樹則要進行旋轉。
    3.2.二、刪除節點存在一個子節點
      此時將子節點上移,替換刪除節點的位置,這個操做勢必會形成所在子樹的高度變化,因此須要依次對父級節點的平衡值作對應修該,若是出現不平衡樹進行旋轉操做。
    3.2.三、刪除節點存在兩個子節點
      這種狀況比較複雜,因爲存在兩個子節點,因此不能簡單的將其中一個子節點提升一級,所以須要在節點的子樹中搜索一個合適的節點進行替換,以前本身構思的時候選擇的是搜尋一個最長子樹的最
      接近刪除節點的值的葉子節點,具體實現的時候發現須要考慮的場景太多,並不適合,參考算法導論上的思路後改爲了搜尋左樹的最大節點,此時只需考慮左樹的狀況便可。實現方法以下:
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.一、左左旋轉  
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.二、右右旋轉 
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.三、左右旋轉
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.四、右左旋轉git

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/AVLTreegithub

相關文章
相關標籤/搜索