【查找結構3】平衡二叉查找樹 [AVL]

在上一個專題中,我們在談論二叉查找樹的效率的時候。不同結構的二叉查找樹,查找效率有很大的不同(單支樹結構的查找效率退化成了順序查找)。如何解決這個問題呢?關鍵在於如何最大限度的減小樹的深度。正是基於這個想法,平衡二叉樹出現了。

 

平衡二叉樹的定義 (AVL—— 發明者爲Adel'son-Vel'skii 和 Landis)

 

平衡二叉查找樹,又稱 AVL樹。 它除了具備二叉查找樹的基本特徵之外,還具有一個非常重要的特點: 的左子樹和右子樹都是平衡二叉樹,且左子樹和右子樹的深度之差的絕對值平衡因子 ) 不超過1。 也就是說AVL樹每個節點的平衡因子只可能是-1、0和1(左子樹高度減去右子樹高度)。


平衡二叉樹的性能優勢:

      很顯然,平衡二叉樹的優勢在於不會出現普通二叉查找樹的最差情況。其查找的時間複雜度爲O(logN)。

 

那麼如何是二叉查找樹在添加數據的同時保持平衡呢?基本思想就是:當在二叉排序樹中插入一個節點時,首先檢查是否因插入而破壞了平衡,若 破壞,則找出其中的最小不平衡二叉樹,在保持二叉排序樹特性的情況下,調整最小不平衡子樹中節點之間的關係,以達 到新的平衡。所謂最小不平衡子樹 指離插入節點最近且以平衡因子的絕對值大於1的節點作爲根的子樹。 

 

平衡二叉樹的操作

 

1. 查找操作

       平衡二叉樹的查找基本與二叉查找樹相同。

 

2. 插入操作

       在平衡二叉樹中插入結點與二叉查找樹最大的不同在於要隨時保證插入後整棵二叉樹是平衡的。那麼調整不平衡樹的基本方法就是: 旋轉 。 下面我們歸納一下平衡旋轉的4中情況

1) 繞某元素左旋轉  

                                 80                                    90  

                                 /  \             左旋               /    \

                               60 90          ---- ->         80     120

                                    /  \                               /  \       /

                                  85 120                    60  85 100

                                        /

                                      100     

                               a)  BST樹                              b ) AVL樹

     分析一下:在插入數據100之前,a圖的B ST樹只有80節點的平衡因子是-1(左高-右高),但整棵樹還是平衡的。加入100之後,80節點的平衡因子就成爲了-2,此時平衡被破壞。需要左旋轉成b 圖。

     當樹中節點X的右孩子的右孩子上插入新元素,且平衡因子從-1變成-2後,就需要繞節點X進行左旋轉。

 

2) 繞某元素右旋轉  

                                100                                   85

                                 /  \               右旋              /    \

                              85  120         ------ ->     60    100  

                              /  \                                      \      /   \

                            60 90                                 80  90 120

                              \

                              80

                             a) B ST樹                                b) AVL樹

     當樹中節點X的左孩子的左孩子上插入新元素,且平衡因子從1變成2後,就需要繞節點X進行右旋轉。

 

3) 繞某元素的左子節點左旋轉,接着再繞該元素自己右旋轉。 此情況下就是左旋與右旋 的結合,具體操作時可以分 解成這兩種操作,只是圍繞點不一樣而已。

                                                      

                            100                             100                                90

                             /  \             左旋            /  \              右旋           /    \

                          80  120       ------>      90  120        ------>     80   100  

                          / \                                  /                                    /  \      \

                       60 90                            80                              60  85  120

                            /                               / \

                          85                            60 85 

      當樹中節點X的左孩子的右孩子上插入新元素,且 平衡因子從1變成2後,就需要 先繞X的左子節點Y左旋轉,接着再繞X右旋轉


4) 繞某元素的右子節點右旋轉,接着再繞該元素自己左旋轉。 此情況下就是 右旋與左旋 的結合,具體操作時可以分解 成這兩種操作,只是圍繞點不一樣而已 。

 

                               80                               80                                       85  

                               /   \              旋          /  \                               /  \     

                            60  100      ------>      60 85            ------->          80 100

                                   /  \                                 \                                   /     /   \       

                                85  120                        100                           60    90 120

                                   \                                   /  \

                                   90                           90  120

       當樹中節點X的右孩子的左孩子上插入新元素,且 平衡因子從-1變成-2後,就需要 先繞X的右子節點Y右旋轉,接着再繞X左旋轉

 

 

平衡二叉樹性能分析


平衡二叉樹的性能優勢:

      很顯然,平衡二叉樹的優勢在於不會出現普通二叉查找樹的最差情況。其查找的時間複雜度爲O(logN)。

 

平衡二叉樹的缺陷:

      (1) 很遺憾的是,爲了保證高度平衡,動態插入和刪除的代價也隨之增加。因此,我們在下一專題中講講《紅黑樹》 這種更加高效的查找結構。

 

      (2) 所有二叉查找樹結構的查找代價都與樹高是緊密相關的,能否通過減少樹高來進一步降低查找代價呢。我們可以通過多路查找樹的結構來做到這一點,在後面專題中我們將通過《多路查找樹/B-樹/B+樹 》來介紹。

 

      (3) 在大數據量查找環境下(比如說系統磁盤裏的文件目錄,數據庫中的記錄查詢 等),所有的二叉查找樹結構(BST、AVL、RBT)都不合適。如此大規模的數據量(幾G數據),全部組織成平衡二叉樹放在內存中是不可能做到的。那麼把這棵樹放在磁盤中吧。問題就來了:假如構造的平衡二叉樹深度有1W層。那麼從根節點出發到葉子節點很可能就需要1W次的硬盤IO讀寫。大家都知道,硬盤的機械部件讀寫數據的速度遠遠趕不上純電子媒體的內存。 查找效率在IO讀寫過程中將會付出巨大的代價。在大規模數據查詢這樣一個實際應用背景下,平衡二叉樹的效率就很成問題了。對這一問題的解決:我們也會在《多路查找樹/B-樹/B+樹 》 將詳細分析。

 

      上面提到的紅黑樹和多路查找樹都是屬於深度有界查找樹(depth-bounded tree —DBT)


平衡二叉樹的插入操作代碼(平衡旋轉)

Java代碼  收藏代碼
  1. package net.hr.algorithm.search;  
  2. /**平衡因子枚舉類*/  
  3. enum B  
  4. alanceFactor{  
  5.     LH("左子樹高"),EH("左右等高"),RH("右子樹高");  
  6.       
  7.     private String illustration="";  
  8.       
  9.     private BalanceFactor(String s){  
  10.         this.illustration=s;  
  11.     }  
  12.       
  13.     public String toString(){  
  14.         return this.illustration;  
  15.     }  
  16. }  
  17. /** 
  18.  * 平衡二叉樹結點 
  19.  */  
  20. class AVLNode<E extends Comparable<E>>{  
  21.     /**結點關鍵字*/  
  22.     E key=null;  
  23.     /**結點的平衡因子*/  
  24.     BalanceFactor bFactor=BalanceFactor.EH;  
  25.     /**結點的直接父親*/  
  26.     AVLNode<E> parent=null;  
  27.     /**結點的左右孩子*/  
  28.     AVLNode<E> lchild,rchild=null;  
  29.       
  30.     AVLNode(E k){  
  31.         this.key=k;  
  32.     }  
  33.     /** 
  34.      * 格式輸出結點 
  35.      */  
  36.     public String toString(){  
  37.         //String fomateStr="";  
  38.         //if(this.lchild==null)  
  39.         String lchildStr=(this.lchild==null)?"null":this.lchild.key.toString();  
  40.         String rchildStr=(this.rchild==null)?"null":this.rchild.key.toString();  
  41.         return this.key+"[lchild="+lchildStr+",rchild="+rchildStr+"]";  
  42.     }  
  43.   
  44. }  
  45. /** 
  46.  * 平衡二叉查找樹 
  47.  * @author heartraid 
  48.  */  
  49. public class AVL<E extends Comparable<E>> {  
  50.   
  51.     /**樹根*/  
  52.     private AVLNode<E> root=null;  
  53.     /**當前樹是否變高*/  
  54.     public boolean isTaller=false;  
  55.       
  56.     public AVL(){  
  57.     }  
  58.       
  59.       
  60.     public boolean insert(E key){  
  61.         System.out.print("插入["+key+"]:");  
  62.         if(key==nullreturn false;  
  63.         if(root==null){  
  64.             System.out.println("插入到樹根。");  
  65.             root=new AVLNode<E>(key);  
  66.             return true;  
  67.         }  
  68.         else{  
  69.             System.out.print("搜索路徑[");  
  70.             return insertAVL(key,root);  
  71.         }  
  72.     }  
  73.       
  74.     private boolean insertAVL(E key,AVLNode<E> node){  
  75.         System.out.print(node.key+" —>");  
  76.         // 樹中存在相同的key,不需要插入  
  77.         if(node.key.compareTo(key)==0){  
  78.             System.out.println("].  搜索有相同關鍵字,插入失敗");  
  79.             isTaller=false;  
  80.             return false;  
  81.         }  
  82.         else{  
  83.             //左子樹搜索  
  84.             if(node.key.compareTo(key)>0){  
  85.                 //當前node的左孩子爲空,則插入到結點的做孩子並修改結點的平衡因子爲LH  
  86.                 if(node.lchild==null){  
  87.                     System.out.println("].  插入到"+node.key+"的左孩子");  
  88.                     AVLNode<E> newNode=new AVLNode<E>(key);  
  89.                     node.lchild=newNode; //設置左孩子結點  
  90.                     newNode.parent=node; //設置父親結點  
  91.                     isTaller=true//樹長高了  
  92.                 }  
  93.                 //左孩子不爲空,則繼續搜索下去  
  94.                 else{  
  95.                     insertAVL(key,node.lchild);  
  96.                 }  
  97.                 //當前如果樹長高了,說明是因爲左孩子的添加改變了平衡因子(左高)。  
  98.                 if(isTaller){  
  99.                     System.out.print("          樹變化了,"+node.key+"的平衡因子變化");  
  100.                     switch(node.bFactor){  
  101.                         //原來結點平衡因子是LH(bf=1),則左高以後bf=2,因此需要做左平衡旋轉  
  102.                         case LH: {  
  103.                             System.out.println("[LH=1 ——> LH=2]. 出現了不平衡現象[左比右高2]");  
  104.                             System.out.println("          ★ 以"+node.key+"爲根將樹進行左平衡處理");  
  105.                             leftBalance(node);  
  106.                             isTaller=false;   
  107.                             break;  
  108.                         }  
  109.                         //原來結點平衡因子是EH(bf=0),則左高了以後bf=1,不需要平衡處理。  
  110.                         case EH:{  
  111.                             System.out.println("[EH=0 ——> LH=1]. 沒有不平衡現象");  
  112.                             node.bFactor=BalanceFactor.LH;  
  113.                             isTaller=true;  
  114.                             break;  
  115.                         }  
  116.                         //原來結點平衡因子是RH(bf=-1),則左高以後bf=0,不需要平衡處理。  
  117.                         case RH:{  
  118.                             System.out.println("[RH=-1 ——> EH=0]. 沒有不平衡現象");  
  119.                             node.bFactor=BalanceFactor.EH;  
  120.                             isTaller=false;  
  121.                             break;  
  122.                         }  
  123.                     }//end switch  
  124.                 }//end if  
  125.             }//end if  
  126.             //右子樹搜索  
  127.             else{  
  128.                 if(node.rchild==null){  
  129.                     System.out.println("].  插入到"+node.key+"的右孩子");  
  130.                     AVLNode<E> newNode=new AVLNode<E>(key);  
  131.                     node.rchild=newNode; //設置右孩子結點  
  132.                     newNode.parent=node; //設置父親結點  
  133.                     isTaller=true//樹長高了  
  134.                 }  
  135.                 else{  
  136.                     insertAVL(key,node.rchild);  
  137.                 }  
  138.                 //當前如果樹長高了,說明是因爲右孩子的添加改變了平衡因子(右高)。  
  139.                 if(isTaller){  
  140.                     System.out.print("          樹變化了,"+node.key+"的平衡因子變化");  
  141.                     switch(node.bFactor){  
  142.                         //原來結點平衡因子是LH(bf=1),則右高以後bf=0,不需要平衡處理。  
  143.                         case LH: {  
  144.                             System.out.println("[LH=1 ——> EH=0]. 沒有不平衡現象");  
  145.                             node.bFactor=BalanceFactor.EH;  
  146.                             isTaller=false;  
  147.                             break;  
  148.                         }  
  149.                         //原來結點平衡因子是EH(bf=0),則右高了以後bf=-1,不需要平衡處理。  
  150.                         case EH:{  
  151.                             System.out.println("[EH=0 ——> RH=-1]. 沒有不平衡現象");  
  152.                             node.bFactor=BalanceFactor.RH;  
  153.                             isTaller=true;  
  154.                             break;  
  155.                         }  
  156.                         //原來結點平衡因子是RH(bf=-1),則右高以後bf=0,因此需要做右平衡旋轉。  
  157.                         case RH:{  
  158.                             System.out.println("[RH=-1 ——> RH=-2]. 出現了不平衡現象[左比右矮2]");  
  159.                             rightBalance(node);  
  160.                             isTaller=false;   
  161.                             break;  
  162.                         }  
  163.                     }//end switch  
  164.                 }//end if(isTaller)  
  165.             }//end else  
  166.             return true;  
  167.         }//end else  
  168.     }  
  169.     /** 
  170.      * 左平衡旋轉處理 
  171.      * 先對node的左子樹進行單左旋處理,在對node樹進行單右旋處理 
  172.      *  
  173.      *     100                      100                     90 
  174.          *     /  \           左旋       /  \          右旋     /  \ 
  175.          *    80  120   ------>  90  120   ------> 80  100   
  176.          *   / \                         /                        /  \     \ 
  177.          *  60 90                   80                     60  85  120 
  178.          *     /                        / \ 
  179.          *    85                    60 85 
  180.      *  
  181.      * @param node 需要做處理的子樹的根結點 
  182.      */  
  183.     private void leftBalance(AVLNode<E> node){  
  184.         // node.parent指向新的孩子結點  
  185.         AVLNode<E> lc=node.lchild;//lc指向node的左孩子結點  
  186.         switch(lc.bFactor){  
  187.             case LH:{  //新結點插入在node的左孩子的左子樹上,則需要單右旋處理  
  188.                 System.out.println("           ┖ 對"+node.key+"進行單右旋轉處理");  
  189.                 node.bFactor=lc.bFactor=BalanceFactor.EH;  
  190.                 rRotate(node);  
  191.                 break;  
  192.             }  
  193.             case RH:{  //新結點插入在node的左孩子的右子樹上,需要雙旋處理  
  194.                 System.out.println("            ┖ 對"+node.key+"的左子樹進行單左旋轉處理,再對其本身樹進行單右循環處理");  
  195.                 AVLNode<E> rd=lc.rchild; //rd指向node左孩子的右子樹根  
  196.                 switch(rd.bFactor){ //修改node與其左孩子的平衡因子  
  197.                     case LH:{  
  198.                         node.bFactor=BalanceFactor.RH;  
  199.                         lc.bFactor=BalanceFactor.EH;  
  200.                         break;  
  201.                     }  
  202.                     case EH:{  
  203.                         node.bFactor=lc.bFactor=BalanceFactor.EH;  
  204.                         break;  
  205.                     }  
  206.                     case RH:{  
  207.                         node.bFactor=BalanceFactor.EH;  
  208.                         lc.bFactor=BalanceFactor.LH;  
  209.                         break;  
  210.                     }  
  211.                 }//switch  
  212.                 rd.bFactor=BalanceFactor.EH;  
  213.                 lRotate(node.lchild);  
  214.                 rRotate(node);  
  215.                 break;  
  216.             }  
  217.         }  
  218.           
  219.     }  
  220.     /** 
  221.      * 右平衡旋轉處理 
  222.      *  
  223.      *    80                         80                        85   
  224.          *   /  \            右 旋      /  \        左 旋        /  \      
  225.          *  60  100    ------>  60   85   ------->   80  100 
  226.          *      /  \                       \                       /   /  \        
  227.          *     85  120                100                 60  90  120 
  228.          *      \                          /  \ 
  229.          *      90                     90  120  
  230.      *  
  231.      * @param node 
  232.      */  
  233.     private void rightBalance(AVLNode<E> node){  
  234.         AVLNode<E> lc=node.rchild;//lc指向node的右孩子結點  
  235.         switch(lc.bFactor){  
  236.             case RH:{  //新結點插入在node的右孩子的右子樹上,則需要單左旋處理  
  237.                 node.bFactor=lc.bFactor=BalanceFactor.EH;  
  238.                 lRotate(node);  
  239.                 break;  
  240.             }  
  241.             case LH:{  //新結點插入在node的右孩子的左子樹上,需要雙旋處理  
  242.                 AVLNode<E> rd=lc.lchild; //rd指向node右孩子的左子樹根  
  243.                 switch(rd.bFactor){ //修改node與其右孩子的平衡因子  
  244.                     case LH:{  
  245.                         node.bFactor=BalanceFactor.EH;  
  246.                         lc.bFactor=BalanceFactor.RH;  
  247.                         break;  
  248.                     }  
  249.                     case EH:{  
  250.                         node.bFactor=lc.bFactor=BalanceFactor.EH;  
  251.                         break;  
  252.                     }  
  253.                     case RH:{  
  254.                         node.bFactor=BalanceFactor.LH;  
  255.                         lc.bFactor=BalanceFactor.EH;  
  256.                         break;    
  257.                     }  
  258.                 }//switch  
  259.                 rd.bFactor=BalanceFactor.EH;  
  260.                 rRotate(node.rchild);  
  261.                 lRotate(node);  
  262.                 break;  
  263.             }  
  264.         }  
  265.     }  
  266.       
  267.       
  268.     /** 
  269.      * 對以node爲根的子樹進行單右旋處理,處理後node.parent指向新的樹根,即旋轉之前 
  270.      * node的左孩子結點 
  271.      *      100<-node.parent           80<-node.parent 
  272.      *      /                                      /  \ 
  273.      *     80             ———>         60   100 
  274.      *    /  \                                  / 
  275.      *   60  85                            85 
  276.      */  
  277.     private void rRotate(AVLNode<E> node){  
  278.           
  279.         AVLNode<E> lc=node.lchild;//lc指向node的左孩子結點  
  280.           
  281.         node.lchild=lc.rchild;  
  282.         lc.rchild=node;  
  283.         if(node.parent==null){  
  284.             root=lc;  
  285.         }  
  286.         else if(node.parent.lchild.key.compareTo(node.key)==0)  
  287.             node.parent.lchild=lc;  
  288.         else node.parent.rchild=lc;  
  289.     }  
  290.     /** 
  291.      * 對以node爲根的子樹進行單左旋處理,處理後node.parent指向新的樹根,即旋轉之前 
  292.      * node的右孩子結點 
  293.      *      100<-node.parent        110<-node.parent 
  294.      *        \                                  /  \ 
  295.      *        110        ————>   100  120 
  296.      *        /  \                               \ 
  297.      *      105  120                      105 
  298.      */  
  299.     private void lRotate(AVLNode<E> node){  
  300.         AVLNode<E> rc=node.rchild;//lc指向node的右孩子結點  
  301.         node.rchild=rc.lchild;  
  302.         rc.lchild=node;  
  303.         if(node.parent==null){  
  304.             root=rc;  
  305.               
  306.         }  
  307.         else if(node.parent.lchild.key.compareTo(node.key)==0)  
  308.                 node.parent.lchild=rc;  
  309.         else node.parent.rchild=rc;  
  310.     }  
  311.       
  312.     /** 
  313.      * 得到BST根節點 
  314.      * @return BST根節點f 
  315.      */  
  316.     public AVLNode<E> getRoot(){  
  317.         return this.root;  
  318.     }  
  319.    
  320.     /** 
  321.      * 遞歸前序遍歷樹 
  322.      */  
  323.     public void preOrderTraverse(AVLNode<E> node){  
  324.         if(node!=null){  
  325.             System.out.println(node);  
  326.             preOrderTraverse(node.lchild);  
  327.             preOrderTraverse(node.rchild);  
  328.         }  
  329.     }  
  330.     /** 
  331.      * 測試 
  332.      * @param args 
  333.      */  
  334.     public static void main(String[] args) {  
  335.         AVL<Integer> avl=new AVL<Integer>();  
  336.         avl.insert(new Integer(80));  
  337.         avl.insert(new Integer(60));  
  338.         avl.insert(new Integer(90));  
  339.         avl.insert(new Integer(85));  
  340.         avl.insert(new Integer(120));  
  341.         avl.insert(new Integer(100));  
  342.       
  343.         System.out.println("前序遍歷AVL:");  
  344.         avl.preOrderTraverse(avl.getRoot());  
  345.   
  346.     }  
  347. }  

 

 

相關問題1:N層平衡二叉樹至少多少個結點

 

假設F(N)表示N層平衡二叉樹的結點個數,則F[1]=1,F[2]=2。而F(N)=F(N-2)+F(N-1)+1

 

爲什麼呢?我們可以這樣考慮,假設現在又一個(N-2)層和(N-1)層的最少結點平衡二叉樹。要構造一棵N層的平衡二叉樹,則只需加入一個根節點,其左右子樹分別(N-2)層和(N-1)層的樹即可 。由於兩個子樹都是最少結點的,所有N層的也是最少結點的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章