算法與數據結構(十一) 平衡二叉樹(AVL樹)(Swift版)

今天的博客是在上一篇博客的基礎上進行的延伸。上一篇博客咱們主要聊了二叉排序樹,詳情請戳《二叉排序樹的查找、插入與刪除》。本篇博客咱們就在二叉排序樹的基礎上來聊聊平衡二叉樹,也叫AVL樹,AVL是發明平衡二叉樹的兩個科學家的名字的縮寫,在此就不作深究了。其實平衡二叉樹就是二叉排序樹的一種,比二叉排序樹多了一個平衡的條件。在一個平衡二叉樹中,一個結點的左右子樹的深度差不超過1html

本篇博客咱們就依照平衡二叉樹的特色,在建立二叉排序樹的同時要保證結點的左右子樹的深度差不超過1的規則。當咱們往二叉排序樹中插入結點時,咱們要對二叉排序樹的平衡性進行檢查,若是因插入這個新的結點二叉排序樹的平衡性被打破了,咱們就得根據打破平衡二叉樹的類型對二叉排序樹進行調整使其再次進入到平衡二叉樹的狀態。固然,在刪除結點時也要二叉樹的平衡進行檢查,發現不平衡時立馬進行糾正。今天博客介紹的就是平衡二叉樹的建立於結點的刪除。廢話少說,進入今天博客的主題。git

 

1、平衡二叉樹的結點github

在博客的第一部分呢,我想先給出平衡二叉樹的結點結構。固然是在上篇博客中的二叉排序樹的結點上進行修改的。下方這個AVLTreeNote就是咱們本篇平衡二叉樹所使用的結點類。該類與二叉排序樹的結點類差很少,就是增長了額外的三個字段。web

  • depth字段用來記錄以該結點爲根結點的樹的深度,由於下方求平衡因子時會使用到該字段。
  • balanceFactor字段就是咱們所說的平衡因子,其實就是左子樹的深度減去右子樹的深度,由於一棵平衡二叉樹的左右子樹的深度差不會超過1,因此一顆平衡二叉樹的節點的平衡因子爲-1,0,或者1。若是爲其餘值,那麼說明該平衡二叉樹已再也不平衡,須要被平衡。
  • fatherNote字段用來指向該結點父節點,咱們在調整二叉樹的平衡時會用到該指針。

上面就是咱們添加的三個字段,下方咱們會分別給出depthbalanceFactor字段的計算方式。算法

  

 

從上面的代碼段中咱們能夠看出,depthbalanceFactor這兩個字段都是計算屬性。接下來咱們將給出這兩個計算屬性具體的計算方法。函數

下方這段代碼就是depth計算屬性的計算方法。計算方法也是比較簡單的,當該結點的左右子樹都存在時,depth就等於左右子樹深度較大的那個值進行加1操做。若是左右子樹有一個爲空的話,那麼depth的值就爲不爲空的那個子樹的高度加1. 若是左右子樹都爲空的話,那麼depth的值就爲0。具體算法以下所示:測試

  

 

接下來咱們來看一下平衡因子balanceFactor的計算方法,以下所示。從下方的代碼段中,咱們也不難看出計算方法也是比較簡單的。若是左右子樹都存在的話,平衡因子balanceFactor就等於左子樹的深度減去右子樹的深度。若是左子樹不爲空,右子樹爲空的話,那麼balanceFactor就等於leftChild.depth + 1, 反之就等於-(rightChild.depth + 1)。若是左右子樹都等於nil的話,那麼平衡因子就爲0。具體算法以下所示:spa

  

 

2、打破平衡的類型以及調整方法3d

平衡二叉樹建立的過程與二叉排序樹的建立過程大致相同,只不過是在新的節點插入到二叉排序樹後,咱們要對其進行平衡的檢查。在檢查過程當中,若是發現不平衡的節點(平衡因子不爲1,0或者-1的狀況)咱們就要對其進行相應的調整,讓其平衡。固然插入節點打破平衡的狀況總結起來總共有四種,也就是本部分要聊的這幾種。大致能夠分爲左左(LL), 左右(LR), 右右(RR), 右左(RL),下方會對每一種狀況進行詳細的介紹,並給出調整平衡的方案,而且給出具體的代碼的實現。指針

 

 一、左左(LL)的狀況

當咱們往一個節點的左(Left)孩子左(Left)孩子添加一個結點時,致使該節點出現了不平衡的狀況,咱們稱之爲這種不平衡的狀況爲左左(LL)的不平衡狀況。下方這個示意圖就是左左狀況以及左左狀況的平衡調整。

在下方示意圖中,咱們插入了一個3節點,致使8的平衡因子變成了2,所以咱們要對8下方的樹進行調整,將其調整爲平衡二叉樹。平衡的具體步驟以及指針的變化方式以下所示,在此就不作過多贅述了。

  

根據上述的示意圖,咱們不難給出左左狀況下調整狀況下的代碼,下方就是具體的代碼實現。若是咱們要對8進行調整,那麼咱們只須要將8所對應的結點指針做爲下方函數的參數便可。具體代碼的意思請結合着上方示意圖的步驟來看,由於每一個結點都會有一個父節點指針,在調整完,不要忘記調整父節點的指向呢,其餘的就不作過多贅述了。

  

 

2.左右(LR)的狀況

該狀況與上述狀況相似,只不過是往一個結點的左子樹的右子樹上添加了一個新結點時致使的不平衡。這種不平衡的方式調整起來會麻煩一些,不過還算是好理解。咱們先將左右的狀況轉換成左左的狀況,而後調用左左的方法來進行調整。下方示意圖就是將左右轉換成左左的狀況,而後再按照左左的狀況進行調整。由於下方示意圖比較明確了,在此就不作過多贅述了。

  

根據上面的示意圖,給出相應的代碼實現並不困難。代碼段中的前幾行代碼就是將左右的狀況轉換成左左的狀況,而後調用咱們上一部分左左的方法進行調整。具體作法以下代碼一致。

  

 

3.右右(RR)的狀況

右右的狀況與左左的狀況極爲類似,就是方向不一樣。右右狀況就是由於往結點的右孩子的右孩子上添加了一個結點,而後致使該結點不平衡的狀況。右右不平衡狀況的調整與左左的步驟相似,只是操做的子節點不一樣。下方示意圖就是右右不平衡狀況的調整步驟。每一步的詳細操做請看示意圖下方的解說。

  

根據上述的示意圖,而後在根據咱們以前左左狀況的代碼,給出右右狀況的代碼要簡單的多。下方的方法就是右右狀況調整的代碼段,其實就是根據左左狀況改的。以下所示:

  

 


4.右左(RL)的狀況

看完上面三步,右左什麼意思就一目瞭然了。右左確定是往該結點的右子樹的左結點上添加一個結點致使以該結點爲根節點的子樹不平衡的狀況。這種狀況下,要進行樹的調整也是比較好作的,咱們就類比左右的那種狀況。咱們先將右左轉換成右右的狀況,而後在按照右右的狀況進行調整便可。下方就是這種狀況調整的完整過程。

  

根據上述過程,給出具體的代碼實現並不困難,下發就是根據上述示意圖給出的具體代碼實現。

  

 

3、平衡二叉樹建立的完整示意圖 

上面聊完調節平衡二叉樹的具體方法後,接下來爲了更直觀的瞭解平衡二叉樹的建立步驟,咱們來一個完整的實例。觀察一下一棵平衡二叉樹從無到有的整個過程,下方就是整個過程。在本部分中,咱們須要將下方的這個序列轉換成平衡二叉樹進行存儲。

  

步驟1:取出3,加入到平衡二叉樹中

由於此刻咱們的平衡二叉樹爲空的,因此咱們將3做爲平衡二叉樹的根節點。此刻3的左右子樹爲空,因此3的平衡因子爲0,此刻咱們現有的二叉樹是平衡的不須要調整。

  

步驟2:將2取出,插入到平衡二叉樹中

由於2比3小,因此將2做爲3的左孩子。由於2是葉子節點,因此2的平衡因子和深度都爲零。而節點3左子樹的深度爲1,右子樹爲空,因此3的平衡因子=左子樹的深度-右子樹深度+1 = 1。從平衡因子中判斷,此刻咱們的二叉樹是平衡二叉樹。

  

步驟3:取出1插入的二叉樹中

由於1比2要小,因此將1做爲2的左孩子。此刻咱們不難計算出節點3的平衡因子爲2,能夠看出以3爲根節點的樹以及不在平衡,咱們須要對其進行調整。咱們不難發現,由於王3的左孩子的左孩子上添加了一個節點,因此屬於左左的狀況,咱們須要按照上一部分左左的狀況處理。將該樹根據左左的狀況再度調整到平衡,具體以下所示:

  

步驟4:將4插入到平衡二叉樹中

下方將4插入到以前的平衡二叉樹中,插入後,二叉樹仍然是平衡的,以下所示:

  

步驟5:將5插入到平衡二叉樹中 

將5插入到平衡二叉樹中後,咱們發現以3爲結點的子樹是由於此節點引發的最小不平衡二叉樹。因此咱們須要以3結點爲基準進行調整。不難看出,此種狀況爲RR的狀況,按照RR的狀況進行調整便可,以下所示:

  

步驟6:將6取出插入到平衡二叉樹中

結點6插入後,咱們不難看出結點2是最小不平衡二叉樹的結點,並且是RR的狀況,咱們須要對其經過RR的狀況進行調整,以下所示: 

  

步驟7:將7插入到平衡二叉樹

結點7插入後,咱們能夠看出,最小不平衡二叉樹的結點是5,因此咱們要以5爲調整結點,按照RR的狀況對咱們不平衡的二叉樹進行調整。具體以下所示:

  

步驟8:將10插入到平衡二叉樹

10插入到平衡二叉樹上,沒有引發不平衡,咱們保持不變。

  

步驟9:將9插入到平衡二叉樹

將9插入後,引發了新的不平。最小不平衡二叉樹的根節點爲7,不平衡的狀況爲RL, 因此咱們能夠根據RL狀況進行調整,以下所示:

步驟10:將8插入

節點8插入後,引發了二叉樹的不平衡,最小不平衡二叉樹的節點爲6。不平衡的狀況爲RL, 咱們能夠根據RL狀況對不平衡的二叉樹進行調整。

  

 

4、建立平衡二叉樹的具體代碼實現

上面的示意圖聊的也挺足的了,接下來咱們就要給出完整的平衡二叉樹建立的代碼了。平衡二叉樹的插入和查找與二叉排序樹的插入和查找相似,只不過平衡二叉樹在插入元素後須要的查找該樹在插入節點後是否是平衡,若是不平衡就要根據相應調整平衡的策略進行調整。若是進行調整在本篇博客的第一部分咱們就已經詳細的給出了。本部分咱們詳細的給出若是在節點插入後尋找最小不平衡二叉樹的根節點

 

1.尋找最小不平衡二叉樹的根節點

開門見山,下方就是尋找最小不平衡二叉樹的根節點的代碼,代碼比較簡單。由於每個節點都有一個指針指向其父節點,因此咱們能夠以插入的結點爲基準,依次往父節點找,知道找到那個平衡因子不爲-1,0或者1的節點爲止。若是找到該結點咱們就調用調整平衡的相關函數便可,下方就是該過程的具體實現。

  

 

2.肯定不平衡的類型

找到不平衡節點後,在對其進行調整以前,咱們須要肯定具體是那種不平衡類型。下方這個代碼段,就是根據不平衡節點來肯定不平衡類型的。具體肯定方式以下:

  • 若是根節點的平衡因子爲2,則說明確定是根節點是左孩子引發的不平衡,因而乎咱們肯定了第一個 。而後咱們在查看根節點的左孩子的平衡因子, 若是爲1,那麼說明是在根節點的左孩子上添加了左孩子致使的不平衡,因此是左左的狀況。同理, 若是是-1,那麼說明是在根節點的左孩子上添加了右孩子,因此是左右的狀況
  • 若是根節點的平衡因子爲-1,那麼很顯然是根節點的右孩子引發的不平衡。參考左左,左右的狀況,若是根節點右孩子的平衡因子爲1,那麼爲右左的狀況,若是根節點的右孩子的平衡因子爲-1,那麼爲右右的狀況。

上述過程用代碼來表示,就以下所示:

  

 

3.具體調整平衡方法的調用

肯定完不平衡類型後,咱們須要根據fixNoBalanceType()方法提供的不平衡類型來調用相應的調整平衡的方法,具體代碼以下所示。在Switch-Case語句中調用的這些方法,咱們在本篇博客的第二部分已經給出了。

  

平衡二叉樹的刪除方法在本篇博客中就不作過多贅述了,在刪除一個結點後,咱們要以該刪除結點的父節點爲準,往上查找不平衡的那個點,而後根據咱們聊的不平衡的狀況進行調整便可。咱們在github上分享的代碼包括平衡二叉樹的結點刪除的代碼,具體請查看github上的代碼實現。

 

5、測試用例

本篇博客的測試用例,咱們就使用第三部分使用的測試用例。在建立完平衡二叉樹後咱們對平衡二叉樹中的結點進行刪除,而後查看二叉樹是否依然平衡。

   

下方是插入結點沒有調用調整平衡的功能所建立的二叉排序樹,咱們能夠看一下其中序遍歷的輸出結果。從結果中咱們就能夠看出,這棵二叉排序樹是不平衡的。

  

接下來咱們就要啓動咱們二叉排序樹調節平衡的功能,下方就是咱們建立的平衡二叉樹輸出的結構,以及每次調整平衡的結點。

  

而下方的輸出結果是刪除某個結點後的輸出結果,由於咱們在刪除結點後,對二叉樹也進行了檢查,若是不平衡咱們要對其進行調節,輸出結果以下所示;

  

 

上述代碼的運行結果以下所示:

本篇博客所涉及的demo在github上的分享地址以下:

github分享地址:https://github.com/lizelu/DataStruct-Swift/tree/master/AVLTree

相關文章
相關標籤/搜索