看圖說話之平衡二叉排序樹

????本文在看圖說話之二叉排序樹的基礎(chǔ)上介紹了平衡二叉排序樹,結(jié)構(gòu)性較好的二叉排序樹其插入和刪除操作的時間復雜度接近Log(N),但是存在某些特定的情況二叉排序樹會退化到單鏈表的情況,比如由順序或者逆序或者基本有序的數(shù)組構(gòu)建的排序二叉樹,此時插入和刪除操就上升到了Log(N)的時間復雜度。下圖所示就是結(jié)構(gòu)性良好和退化的二叉排序樹。

圖1 最優(yōu)BST樹形結(jié)構(gòu)和最差BST樹形

????為了解決二叉排序樹的這種退化的情況,在其基礎(chǔ)上提出了平衡二叉排序樹,實現(xiàn)平衡二叉排序樹的方法有很多種,其中最基礎(chǔ)就是AVL樹,本文詳細的介紹下AVL樹實現(xiàn)平衡的基本原理以保證二叉排排序樹具有Log(N)的時間復雜界。

一丶平衡二叉排序樹

????平衡二叉排序樹如其名字在二叉樹上約定了平衡條件,通過下圖這個平衡二叉樹和非平衡二叉樹來說明平衡二叉樹的平衡條件。


圖2 平衡和非平衡二叉樹

????平衡性,是指每個節(jié)點的左子樹高度和右子樹高度之差不超過1,即 Math.abs(Hieght(node.left) –Height(node.right))<1。對于圖2中的平衡二叉樹而言,其上每個節(jié)點都滿足這個性質(zhì)。圖2中的非平衡樹,之所以不是平衡樹,是因為在根節(jié)點處平衡性遭到了破壞,其左子樹高度和右子樹高度之差為2。

二丶AVL樹平衡性調(diào)整策略

????我覺得研究AVL樹調(diào)整平衡性策略的正確姿勢應(yīng)該包括兩步,第一步研究導致二叉排序樹不平衡的情況是哪幾種。第二步針對具體的不平衡情況如何調(diào)整。
先研究二叉排序樹的不平衡情況,然后針對二叉排序樹的不平衡特點給出解決方案:

(1) 向節(jié)點的左兒子的左子樹插入元素導致的平衡性破壞。


圖3 第一種非平衡情況

????可以看到,插入元素1后,是節(jié)點10處的平衡性遭到了破壞。這里要強調(diào)一下,在更一般的情況下,插入元素有破壞平衡性的可能,而平衡性被破壞的節(jié)點可能發(fā)生在插入路徑上的每一個節(jié)點,可能是父節(jié)點,可能是其他節(jié)點。
對于該種平衡性破壞給出的解決方法稱為左旋轉(zhuǎn)其過程如下圖所示:


圖 4Single左旋轉(zhuǎn)平衡性調(diào)整

????通過圖4可以清晰的看出Single左旋轉(zhuǎn)調(diào)整平衡性的特點,利用文字描述過于拗口,利用偽代碼描述如下:假設(shè)平衡性被破壞的節(jié)點是node。
//調(diào)整node節(jié)點平衡性
node = SingleLeftRotation(node);
//balacne函數(shù)定義如下
public node SingleLeftRotation( (Nodenode){
        if(node ==null) return null;
           Node newRoot= node.left;
           node.left = newRoot.right;
           newRoot.right = node;
           return newRoot;
}

(2)向節(jié)點的右兒子的右子樹插入元素導致的平衡性破壞。

圖5第二種非平衡情況

該種情況下的非平衡性情況十分清晰,并且和第一種非平衡性情況是對稱的,不妨成這種非平衡性破壞為“右右“,第一種非平衡性破壞為”左左“。該種情況下的調(diào)整過程如圖所示,這調(diào)整過程稱為single右旋轉(zhuǎn)。


圖6 single右旋轉(zhuǎn)調(diào)整

上述調(diào)整過程用偽代碼實現(xiàn)如下:

//調(diào)整node節(jié)點平衡性
node = SingleRightRotation(node);
//balacne函數(shù)定義如下
public node SingleRightRotation( (Nodenode){
        if(node==null) return null;
           Node newRoot= node.right;
           node.right = newRoot.left;
           newRoot.left= node;
           return newRoot;
}

(3)向節(jié)點的左兒子的右子樹插入元素導致平衡性破壞


圖7 第三種平衡破壞情況

這種不平衡性的情況,形象的將其稱之為“左右“不平衡,該情況的不平衡的過程如下圖所示:


圖 8 double左旋轉(zhuǎn)調(diào)整

????圖8清晰的說明了調(diào)整平衡性的過程,在掌握的single左旋轉(zhuǎn)和single右旋轉(zhuǎn)的基礎(chǔ)上看懂上述過程絲毫不苦難,上述過程稱為double左旋轉(zhuǎn)利用偽代碼描述如下:
//調(diào)整node節(jié)點平衡性
node = dounleLeftRotation (node);
//balacne函數(shù)定義如下
public node dounleLeftRotation(Nodenode){
           node.left=singleRightRotation(node.left);
           node = singleLeftRotation(node);
           return node;
}

(4)向節(jié)點的右兒子的左子樹插入元素導致平衡性破壞


圖9 第四種不平衡情況

該種平衡情況是向右兒子的左子樹插入元素導致不平衡,這種情況形象的稱之為“右左“不平衡,和”左右“不平衡是對稱的處理的手段都是類似的。下圖詳細的描述了這種情況如何調(diào)整平衡


圖10 double右旋轉(zhuǎn)

通過圖10可以清晰的看到調(diào)整平衡的過程,上述過程和double左旋轉(zhuǎn)是對稱的操作,利用偽代碼描述如下:
//調(diào)整node節(jié)點平衡性
node = dounleRightRotation (node);
//balacne函數(shù)定義如下
public node dounleLeftRotation(Nodenode){
           node.left=singleLeftRotation(node.left);
           node = singleRightRotation(node);
           return node;
}
三丶AVL樹完整的java實現(xiàn)
public classAVLTree {
    publicNode root = null;
     publicstatic void main(String []args){
       int[] elements = new int[]{1,2,3,4,5};
       AVLTree avl = new AVLTree();
       /****測試插入,建樹和中序遍歷操作***/
       avl.buildTree(elements);
       /********測試二叉樹的高度***************/
       System.out.println(avl.root.height);
    //  bst.midTrace();
    //  System.out.println();
       /****測數(shù)刪除操作之無兒子***************/
    //  bst.delete(9);
    //  bst.midTrace();
       /****測數(shù)刪除操作之只有一個兒子***************/
    //  bst.delete(4);
    //  bst.midTrace();
       /****測數(shù)刪除操作只有兩個兒子***************/
    //  bst.delete(5);
    //  bst.midTrace();
    }
   
    //節(jié)點類型定義
    publicstatic class Node{
       publicint element;
       publicNode left;
       publicNode right;
       //平衡二叉排序樹,為了保證平衡,對每個節(jié)點維護了高度信息
       publicint  height;
       publicNode(int element){
           this.element = element;
           left = null;
           right = null;
           height = 0;
       }
    }
    //外部使用的插入方法
    publicvoid insert(int element){
       root=insert(element,root);
    }
    //內(nèi)部使用的插入方法
    private  Node insert(int element,Node root){
       if(root==null)
           return new Node(element);
       intdelta = element-root.element;
       if(delta<0){
           root.left = insert(element,root.left);
       }elseif(delta>0){
           root.right = insert(element,root.right);
       }else{
           //不做處理
       }
       returnbalanced(root);
    }
    //外部使用的建樹方法
    publicvoid buildTree(int [] elements){
       if(root==null){
           for(int i=0;i<elements.length;i++){
              insert(elements[i]);
           }
       }else{
           //若樹以存在,則不能建
       }
    }
    publicvoid delete(int element){
       delete(element,root);
    }
    publicNode delete(int element,Node root){
       //未定位到刪除元素
       if(root==null) return null;
       intdelta = element-root.element;
       if(delta<0){
           root.left = delete(element,root.left);
       }elseif(delta>0){
           root.right =delete(element,root.right);
       }else{
           if(root.left!=null&&root.right!=null){
              Node node = findMin(root.right);
              root.element =node.element;
              root.right = delete(node.element,root.right);
           }else{
              root = root.left!=null?root.left:root.right;
           }
       }
       returnbalanced(root);
    }
    //中序遍歷二叉樹
    publicvoid midTrace(){
       if(root!=null){
           print(root);
       }
    }
    privatevoid print(Node node){
       if(node!=null){
           print(node.left);
           System.out.print(node.element+" ");
           print(node.right);
       }
    }
    //找到該節(jié)點下子樹的最小節(jié)點。
    publicNode findMin(Node node){
       if(node.left==null)
           return node;
       else
          
           return findMin(node.left);
    }
    publicNode balanced(Node node){
       node.height= Math.max(getHeight(node.left),getHeight(node.right))+1;
       if(getHeight(node.left)-getHeight(node.right)>1){
           if(getHeight(node.left.left)>getHeight(node.left.right)){
              node = singleLeftRotation(node);
           }else{
              node = doubleLeftRotation(node);
           }
       }
       if(getHeight(node.right)-getHeight(node.left)>1){
           if(getHeight(node.right.left)>getHeight(node.right.right)){
              node =doubleLeftRotation(node);
           }else{
              node = singleRightRotation(node);
           }
       }
       returnnode;
    }
    publicint getHeight(Node node){
       returnnode==null?-1:node.height;
    }
    publicNode singleLeftRotation(Node node){
       Node newRoot = node.left;
       node.left = newRoot.right;
       //左右子樹變化更新高度
       node.height = Math.max(getHeight(node.left),getHeight(node.right))+1;
       newRoot.right=node;
       //左右子樹變化更新高度
       newRoot.height=  Math.max(getHeight(newRoot.left),getHeight(newRoot.right))+1;
       returnnewRoot;
    }
    publicNode singleRightRotation(Node node){
       Node newRoot = node.right;
       node.right = newRoot.left;
       //左右子樹變化更新高度
       node.height = Math.max(getHeight(node.left),getHeight(node.right))+1;
       newRoot.left=node;
       //左右子樹變化更新高度
       newRoot.height=  Math.max(getHeight(newRoot.left),getHeight(newRoot.right))+1;
       returnnewRoot;
    }
    publicNode doubelRightRotation(Node node){
       node.right = singleLeftRotation(node.right);
       returnsingleRightRotation(node);
    }
    publicNode doubleLeftRotation(Node node){
       node.left = singleRightRotation(node.left);
       returnsingleLeftRotation(node);
    }
}

Reference:
[1] 數(shù)據(jù)結(jié)構(gòu)與算法 java語言描述
[2] 原文博客鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內(nèi)容