平衡二叉樹,B樹


AVL樹(平衡二叉樹)

AVL樹本質上是一顆二叉查找樹,但是它又具有以下特點:

1、   它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1

2、   左右兩個子樹都是一棵平衡二叉樹。

AVL樹解決了普通二叉查找樹演化為線性導致線性查找時間問題

AVL樹平衡的操作主要有:

1、左-左型:做右旋。

2、右-右型:做左旋轉。

3、左-右型:先做左旋,后做右旋。

4、右-左型:先做右旋,再做左旋。

右旋:把左孩子變成父節點,原來的父節點變成左孩子的右孩子,見下圖(左左型)。

              

左旋:把右孩子變成父節點,原來的父節點變成右孩子的左孩子,見下圖(右右型)。

              

下面是右左型的平衡操作

                

實現代碼(沒有編寫測試用例,可能會有一些邊界未處理問題)

public class AVL {

    
    public class AVLNode{
        
        int height;
        AVLNode leftChild;
        AVLNode rightChild;
        AVLNode parent;
        int value;
        
    }
    
    public int getHeight(AVLNode node) {
        
        if (node == null) {
            return 0;
        }else {
            return node.height;
        }
    }
    
    /**
     * 左左型不平衡,進行右旋操作
     * @param LL_type_node
     */
    public void R_Rotate(AVLNode LL_type_node) {
        
        if (LL_type_node != null) {
            
            AVLNode left = LL_type_node.leftChild; // 取出要旋轉節點的左孩子
            AVLNode parent = LL_type_node.parent;  // 取出要旋轉節點的父節點
            
            LL_type_node.leftChild = left.rightChild; // 左孩子的右孩子變成父節點的左孩子
            left.rightChild.parent = LL_type_node;
        
            left.rightChild = LL_type_node; // 左孩子的右節點變為其原來的父節點
            
            // 更新父節點
            LL_type_node.parent = left;
            left.parent = parent;
            
            // 檢查原來要旋轉節點是其父節點的左孩子還是右孩子
            if (parent.leftChild == LL_type_node) {
                parent.leftChild = left;
            }else if (parent.rightChild == LL_type_node) {
                parent.rightChild = left;
            }
            
            LL_type_node.height = Math.max(getHeight(LL_type_node.leftChild), 
                    getHeight(LL_type_node.rightChild)) + 1;
            left.height = Math.max(getHeight(left.leftChild), 
                    getHeight(left.rightChild)) + 1;
            parent.height = Math.max(getHeight(parent.leftChild), 
                    getHeight(parent.rightChild)) + 1;
        }
        
    }
    
    /**
     * 右右型不平衡,進行左旋操作
     * @param LL_type_node
     */
    public void L_Rotate(AVLNode RR_type_node) {
        
        if (RR_type_node != null) {
            
            AVLNode right = RR_type_node.leftChild; // 取出要旋轉節點的右孩子
            AVLNode parent = RR_type_node.parent;  // 取出要旋轉節點的父節點
            
            RR_type_node.rightChild = right.leftChild; // 右孩子的左孩子變成父節點的右孩子
            right.leftChild.parent = RR_type_node;
        
            right.leftChild = RR_type_node; // 左孩子的右節點變為其原來的父節點
            
            // 更新父節點
            RR_type_node.parent = right;
            right.parent = parent;
            
            // 檢查原來要旋轉節點是其父節點的左孩子還是右孩子
            if (parent.leftChild == RR_type_node) {
                parent.leftChild = right;
            }else if (parent.rightChild == RR_type_node) {
                parent.rightChild = right;
            }
            
            RR_type_node.height = Math.max(getHeight(RR_type_node.leftChild), 
                    getHeight(RR_type_node.rightChild)) + 1;
            right.height = Math.max(getHeight(right.leftChild), 
                    getHeight(right.rightChild)) + 1;
            parent.height = Math.max(getHeight(parent.leftChild), 
                    getHeight(parent.rightChild)) + 1;
        }
    }

    /**
     * 左右型,先左旋,后右旋
     * @param LR_type_node
     */
    public void L_R_Rotate(AVLNode LR_type_node) {
        if (LR_type_node != null) {
            L_Rotate(LR_type_node.leftChild);
            R_Rotate(LR_type_node);
        }
    }
    
    /**
     * 左右型,先右旋,后左旋
     * @param LR_type_node
     */
    public void R_L_Rotate(AVLNode RL_type_node) {
        if (RL_type_node != null) {
            R_Rotate(RL_type_node.rightChild);
            L_Rotate(RL_type_node);
        }
    }
    
    public AVLNode insertAVLTree(AVLNode root, AVLNode toInsert) {
        
        AVLNode rtRoot = root;
        
        if (toInsert != null) {
            if (root != null) {
                
                if (root.value > toInsert.value) {
                    rtRoot.leftChild = insertAVLTree(root.leftChild, toInsert);
                    //插入后看看是否需要調整
                    if (getHeight(rtRoot.leftChild) - 
                            getHeight(rtRoot.rightChild) == 2) {
                        // 左左型,進行右旋
                        if (toInsert.value < rtRoot.leftChild.value) {
                            R_Rotate(rtRoot);
                        }else {
                            // 左右型
                            L_R_Rotate(rtRoot);
                        }
                    }
                }else if (root.value < toInsert.value) {
                
                    rtRoot.rightChild = insertAVLTree(root.rightChild, toInsert);
                    //插入后看看是否需要調整
                    if (getHeight(rtRoot.rightChild) - 
                            getHeight(rtRoot.leftChild) == 2) {
                        // 右右型,進行左旋
                        if (toInsert.value > rtRoot.rightChild.value) {
                            L_Rotate(rtRoot);
                        }else {
                            // 右左型
                            R_L_Rotate(rtRoot);
                        }
                    }
                }
            }else {
                rtRoot = toInsert;
            }
        }
        rtRoot.height = Math.max(getHeight(rtRoot.leftChild), 
                getHeight(rtRoot.rightChild)) + 1;
        return rtRoot;
    }
    
    public void deleteNode(AVLNode root, int value) {
        
        if (root != null) {
            if (value < root.value) {
                deleteNode(root.leftChild, value);
                int lh = getHeight(root.leftChild);
                int rh = getHeight(root.rightChild);
                
                if (rh - lh == 2) {
                    if(getHeight(root.rightChild.rightChild) > 
                    getHeight(root.rightChild.leftChild)){
                        // 右右型
                        L_Rotate(root);
                    }else{
                        // 右左型
                        R_L_Rotate(root);
                    }
                }
                
            }else if (value > root.value) {
                deleteNode(root.rightChild, value);
                int lh = getHeight(root.leftChild);
                int rh = getHeight(root.rightChild);
                
                if (lh - rh == 2) {
                    if(getHeight(root.leftChild.leftChild) > 
                    getHeight(root.leftChild.rightChild)){
                        // 左左型
                        R_Rotate(root);
                    }else{
                        // 左右型
                        L_R_Rotate(root);
                    }
                }
            }else {
                
                AVLNode toDeleteParent = root.parent;
                
                if (root.leftChild == null) {
                    /* 要刪除節點的左子樹為空,那么該節點可以直接刪除而不用找后繼節點(因為后繼就是它的右
                     * 孩子,如果它的右孩子也為空,那說明它是個葉子節點,可直接刪)
                     */
                    if (root == toDeleteParent.leftChild) {
                        toDeleteParent.leftChild = root.rightChild;
                        if (toDeleteParent.leftChild != null) {
                            toDeleteParent.leftChild.parent = toDeleteParent;
                        }
                    }else {
                        toDeleteParent.rightChild = root.rightChild;
                        if (toDeleteParent.rightChild != null) {
                            toDeleteParent.rightChild.parent = toDeleteParent;
                        }
                    }
                }else if (root.rightChild == null) {
                    /* 要刪除節點的右子樹為空,那么該節點可以直接刪除而不用找后繼節點(因為后繼就是它的父
                     * 節點,如果它的左孩子也為空,那說明它是個葉子節點,可直接刪)
                     */
                    if (root == toDeleteParent.leftChild) {
                        toDeleteParent.leftChild = root.leftChild;
                        if (toDeleteParent.leftChild != null) {
                            toDeleteParent.leftChild.parent = toDeleteParent;
                        }
                    }else {
                        toDeleteParent.rightChild = root.leftChild;
                        if (toDeleteParent.rightChild != null) {
                            toDeleteParent.rightChild.parent = toDeleteParent;
                        }
                    }
                }else {
                    int next = getHeight(root);
                    root.value = next; // 用后繼代替待刪除節點
                    deleteNode(root.rightChild, next); // 刪除后繼
                    
                    int lh = getHeight(root.leftChild);
                    int rh = getHeight(root.rightChild);
                    
                    if (lh - rh == 2) {
                        if(getHeight(root.leftChild.leftChild) > 
                        getHeight(root.leftChild.rightChild)){
                            // 左左型
                            R_Rotate(root);
                        }else{
                            // 左右型
                            L_R_Rotate(root);
                        }
                    }
                }
            }
            
            root.height = Math.max(getHeight(root.leftChild), 
                    getHeight(root.leftChild)) + 1;
        }
        
    }
    
    /**
     * 取得當前節點的后繼
     * @param currentNode
     * @return
     */
    public int getNextNodeValue(AVLNode currentNode) {
        if ((currentNode = currentNode.rightChild) != null) {
            
            while(currentNode.leftChild != null){
                currentNode = currentNode.leftChild;
            }
        }
        
        return currentNode.value;
    }
    
}
View Code

紅黑樹

紅黑樹是近似平衡的二叉查找樹,它相比AVL樹在調整的時候可以減少旋轉次數,它具有以下特點

  1、節點是紅色或黑色。

2、 根節點是黑色。

3、 每個紅色節點的兩個子節點都是黑色。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點)

4、 從任一節點到其每個葉子的所有路徑都包含相同數目的黑色節點。

這些約束強制了紅黑樹的關鍵性質: 從根到葉子的最長的可能路徑不多於最短的可能路徑的兩倍長。結果是這個樹大致上是平衡的。因為操作比如插入、刪除和查找某個值的最壞情況時間都要求與樹的高度成比例,這個在高度上的理論上限允許紅黑樹在最壞情況下都是高效的,而不同於普通的二叉查找樹。

紅黑樹的插入刪除過程較為復雜,詳細請參考以下博客

紅黑樹插入:https://www.cnblogs.com/CarpenterLee/p/5503882.html

紅黑樹刪除:https://www.cnblogs.com/CarpenterLee/p/5525688.html

B

一棵mB(balanced tree of order m)是一棵平衡的m路搜索樹。它或者是空樹,或者是滿足下列性質的樹:

1、根結點至少有兩個子女;

2、每個非根節點所包含的關鍵字個數 j 滿足:┌m/2┐ - 1 <= j <= m - 1;即每個節點最多有m棵子樹(至於為什么去m/2的上界,我的理解應該是為了樹的平衡性質以及樹的高度不會太高)

3、除根結點以外的所有結點(不包括葉子結點)的度數正好是關鍵字總數加1,故內部子樹個數 k 滿足:┌m/2┐ <= k <= m

4、所有的葉子結點都位於同一層。

下圖是一棵4B樹(指針域未畫出來),4階指的是某個節點最多有4棵子樹。

B樹節點的插入:往上圖的4階B樹插入關鍵字6。插入6,進入根節點的左子樹,找到合適位置插入,此時關鍵字6所在的節點包含 {5, 6, 8, 9} 共NUM = 4個關鍵字,不滿足4階B樹性質(最多有4-1個關鍵字),進行分裂,分裂點為 NUM / 2向上取整,故在關鍵字6,插入后的結果為見下圖。插入操作要注意的就是分裂的關鍵字是作為新的節點,加入其父節點中,然后判斷此時父節點是否需要分裂。

B樹節點的刪除:

相比B樹的插入稍微復雜一些,當從B-樹中刪除一個關鍵字Ki時,總的分為以下兩種情況:

如果該關鍵字所在的結點不是最下層的非葉子結點,則先需要把此關鍵字與它在B樹中后繼對換位置,即以指針Pi所指子樹中的最小關鍵字Y代替Ki,然后在相應的結點中刪除Y。

如果該關鍵字所在的結點正好是最下層的非葉子結點,這種情況下,會有以下兩種可能:

  1、若該關鍵字Ki所在結點中的關鍵字個數不小於┌m/2┐,則可以直接從該結點中刪除該關鍵字和相應指針即可。

  2、若該關鍵字Ki所在結點中的關鍵字個數小於┌m/2┐,則直接從結點中刪除關鍵字會導致此結點中所含關鍵字個數小於┌m/2┐-1 。這種情況下,需考察該結點在B樹中的左或右兄弟結點,從兄弟結點中移若干個關鍵字到該結點中來(這也涉及它們的雙親結點中的一個關鍵字要作相應變化),使兩個結點中所含關鍵字個數基本相同;但如果其兄弟結點的關鍵字個數也很少,剛好等於┌m/2┐-1 ,這種移動則不能進行,這種情形下,需要把刪除了關鍵字Ki的結點、它的兄弟結點及它們雙親結點中的一個關鍵字合並為一個結點。能力有限,無法畫動圖,上面的理論也比較難懂,建議直接在網上找個視頻教程看看了解刪除的過程。

還有一種B樹的變形B+樹,它與B樹的區別在於非葉節點僅作為索引作用,不保存具體信息,並且所有的葉節點構成一條有序鏈表。它相比B樹的優點在於一個內存塊可以存儲更多的關鍵字(因為B+樹只存索引,而B樹還保存着信息,所有B+樹關鍵字占用內存少),減少了磁盤io,另外由於所有的葉子節點構成有序鏈表,所有可以方便的進行范圍查詢。更多詳細請參考:https://blog.csdn.net/qq_26222859/article/details/80631121


免責聲明!

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



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