算法與數據結構(三) 二叉樹的遍歷及其線索化(Swift版)

前面兩篇博客介紹了線性表的順序存儲與鏈式存儲以及對應的操做,而且還聊了棧與隊列的相關內容。本篇博客咱們就繼續聊數據結構的相關東西,而且所涉及的相關Demo依然使用面嚮對象語言Swift來表示。本篇博客咱們就來介紹樹結構的一種:二叉樹。在以前的博客中咱們簡單的聊了一點樹的東西,樹結構的特色是除頭節點之外的節點只有一個前驅,可是能夠有一個或者多個後繼。而二叉樹的特色是除頭結點外的其餘節點只有一個前驅,節點的後繼不能超過2個。git

本篇博客,咱們只對二叉樹進行討論。在本篇博客中,咱們對二叉樹進行建立,而後進行各類遍歷,最後將二叉樹進行線索化。在Demo實現以前,咱們先對二叉樹的概念及其特性進行介紹,而後在給出具體的代碼實現。github

 

1、二叉樹的特性算法

上面咱們已經提到過,一個除頭結點外,每一個節點只有一個前驅,有零到兩個後繼的樹即爲二叉樹。在二叉樹中,一個節點能夠有左節點或者左子樹,也能夠有右節點或者右子樹。一些特殊的二叉樹,好比斜二叉樹、滿二叉樹、徹底二叉樹等等就不作過多贅述了。說這麼多,不如看一張圖來的直觀。下方就是一個典型的二叉樹。數組

  

瞭解二叉樹,理解其特性仍是比較重要的。基於二叉樹自己的邏輯結構,下方是二叉樹這種數據結構所具有的特性。數據結構

  • 特性1:在二叉樹的第i層上至多有2^(i-1)(i >= 1)個節點
    • 這一特性比較好理解,若是層數是從零開始數的話,那麼低i層上的節點數就是2^i,由於二叉樹層與層之間的節點數是以2的指數冪進行增加的。若是根節點算是第0層的話,那麼第n層的節點數就是2^n次冪。
  • 特性2:深度爲k的二叉樹至多有2^k-1(k>=1)個節點
    • 這一特性也是比較好理解的, 由數學上的遞加公式就能夠很容易的推出來。由特性1易知每層最多有多少個節點,那麼深度爲k的話,說明一共有k層,那麼共有節點數爲:2^0 + 2^1 + 2^2 + 2^(k-1) = 2^k - 1。
  • 特性3:二叉樹的葉子節點數爲n0, 度爲2的節點數爲n2, 那麼n0 = n2 + 1
    • 這一特性也不難理解,推出n0 = n2 + 1這個公式並不難。咱們假設葉子節點,也就是度數爲0的節點的個數爲n0, 度數爲1的節點爲n1, 度數爲2的節點n2。那麼二叉樹的節點總數 n = n0 + n1 + n2。由於除了根節點外其他的節點入度都爲1,因此二叉樹的度數爲n-1,固然度的個數可使用出度來算,即爲2*n2+n1,因此n-1=2*n2+n1。以n=n0+n1+n2與n-1=2*n2+n1這兩個公式咱們很容易的推出n0 = n2 + 1。
  • 特性4:具備n個結點的徹底二叉樹的深度爲log2n + 1 (向下取整,好比3.5,就取3)
    • 這個特性也是比較好理解的,基於徹底二叉樹的特色,咱們假設徹底二叉樹的深度爲k, 那麼二叉樹的結點個數的範圍爲2(k-1)-1 <= n <= 2k-1。由這個表達式咱們很容易推出特性4。

 

2、二叉樹的建立函數

上面介紹完二叉樹的特性後,接下來咱們要作的就是將二叉樹進行存儲。固然通常存儲二叉樹的結構是以二叉鏈表的形式來存儲的。二叉鏈表的結構相似於雙向鏈表,二叉鏈表的節點也是有兩個結點指針的,一個指向左子樹,一個指向右子樹。接下來咱們要使用二叉鏈表的形式來存儲咱們的二叉樹。測試

 

1.先序建立二叉樹spa

在建立二叉樹以前,咱們先了解一個什麼是先序遍歷。先序遍歷就是先遍歷根結點,而後遍歷左子樹,最後遍歷右子樹。咱們就以此規則來建立二叉樹,換句話說,咱們有一個數據序列,將依照這個序列按照先序建立二叉樹的原則來建立該二叉樹,先建立二叉樹的根節點,而後再建立二叉樹的左子樹,而後再建立右子樹。而這個建立的二叉樹的先序遍歷的結果就是咱們以前輸入的數據序列。下方就是先序建立二叉樹的原理圖。3d

  

從上面的分析咱們不難看出,咱們要先建立根節點,而後建立左子樹,最後建立右子樹。由於左子樹和右子樹都是二叉樹,因此建立左子樹和右子樹是原問題的子問題。也就是說子問題與原問題解決方案一致,這種狀況下就可使用遞歸的思想來解決。咱們先將上述二叉樹的結構轉換成二叉鏈表的形式直觀的感覺一下,而後再將其使用代碼的形式進行表示便可。下方這個截圖就是上述二叉樹的二叉鏈表的存儲結構。每一個節點都有左指針與右指針,分別本身的左子節點和右子節點。若是沒有子節點就爲空。指針

  

2.先序建立二叉樹的代碼實現

上面咱們分析了二叉鏈表的結構,接下來咱們就來建立二叉鏈表了。首先咱們得建立二叉鏈表的節點類,以前咱們用C語言來實現二叉樹的時候,是使用的結構體來實現的二叉鏈表的節點,由於C語言是面向過程的語言,根本就沒有類這個概念。由於此刻咱們是使用的面嚮對象語言,因此我就可使用一個類來表示咱們二叉鏈表的節點了。下方這個GeneralBinaryTreeNote就是二叉鏈表的類。data屬性存儲的就是樹節點中所存儲的值,而leftChild就指向左節點的內存地址,而rightChild就指向右節點的內存地址。

  

上面咱們已經說過,先序建立二叉樹的過程是能夠用遞歸來表示的,因此咱們就遞歸的去建立咱們想要建立的二叉樹。下方就是先序建立二叉樹的核心代碼,self.items中存儲的是二叉樹的節點信息。通過下方函數的遞歸執行,就能夠建立出咱們想要的二叉樹了。從下方的遞歸過程咱們就明顯的能看出是先序建立的二叉樹。先建立的根節點,而後遞歸建立左子樹,而後在遞歸建立右子樹。

  

下方就是咱們二叉樹的初始化過程,下方在初始化過程當中主要是調用上方的這個方法,將items數組中存儲的值轉換成二叉鏈表的存儲結構。items數組中的空字符串,代表該節點爲空。

  

其實上面實例中所建立的二叉樹的結構就是下方的結構。

  

 

3、二叉樹的遍歷

聊二叉樹怎麼能沒有二叉樹的遍歷呢,下方就會給出幾種常見的二叉樹的遍歷方法。在遍歷二叉樹的方法中通常有先序遍歷,中序遍歷,後續遍歷,層次遍歷。本篇博客主要給出前三種遍歷方式,而層次遍歷會在圖的部分進行介紹。二叉樹的層次遍歷其實與圖的廣度搜索是同樣的,因此這部分放到圖的相關博客中介紹。下方會給出幾種遍歷的具體方式,而後給出具體的代碼實現。

二叉樹的先、中、後遍歷,這個先中後指的是遍歷根節點的前後順序。先序遍歷:根左右,中序遍歷:左根右,後序遍歷:左右根。下方將詳細介紹到。

 

1.先序遍歷

關於先序遍歷,上面已經介紹過一些了,接下來再進行細化一下。先序遍歷,就是先遍歷根節點而後再遍歷左子樹,最後遍歷右子樹。下圖就是咱們上面建立的二叉樹的先序遍歷的順序,由下方的示例圖就能夠看出先序遍歷的規則。一句話總結下方的結構圖:根節點->左節點->右節點。下方先序遍歷的順序爲:A B D   E   C  F   。

  

上面給出了原理,接下來又到了代碼實現的時候了。在樹的遍歷時,咱們依然是採用遞歸的方式,由於不管是左子樹仍是右子樹,都是二叉樹的範疇。因此在進行二叉樹遍歷時,可使用遞歸遍歷的形式。而先序遍歷莫非就是先遍歷根節點,而後遞歸遍歷左子樹,最後遍歷右子樹。下方就是先序遍歷的代碼實現。在下方代碼中,若是左節點或者右節點爲空,那麼咱們就輸出「空」。

  

 

2.中序遍歷

中序遍歷,與先序遍歷的不一樣之處在於,中序遍歷是先遍歷左子樹,而後遍歷根節點,最後遍歷右子樹。一句話總結:左子樹->根節點->右子樹。下方就是咱們以前建立的樹的中序遍歷的結構圖以及中序遍歷的結果。

   

中序遍歷的代碼實現與先序遍歷的代碼實現相似,都是使用遞歸的方式來實現的,只不過是先遞歸遍歷左子樹,而後遍歷根節點,最後遍歷右子樹。下方就是中序遍歷的代碼具體實現。

  

 

3.後序遍歷

接下來聊一下二叉樹的後序遍歷。若是上面這兩種遍歷方式理解的話,那麼後序遍歷也是比較好理解的。後序遍歷是先遍歷左子樹,而後再遍歷右子樹,最後遍歷根節點。與上方的表示方法一直,首先咱們給出表示圖,以下所示:

  

後序遍歷的代碼就不作過多贅述了,與以前兩種依然相似,只是換了一下遍歷的順序。下方就是二叉樹後序遍歷的代碼實現。

  

 

四、層次遍歷

二叉樹的層次遍歷就不是二叉樹這種數據結構所獨有的了。後面的博客中咱們會介紹到圖這種數據結構,在圖中有一個廣度搜索,放到二叉樹中就是層次遍歷。也就是說二叉樹的層次遍歷,就是圖中以二叉樹的根節點爲起始節點的廣度搜索(BFS)。本篇博客就不給出具體的代碼了,後面的博客會給出BFS的具體算法。固然在以前的博客中有圖的BFS以及DFS。不過是C語言的實現。下方就是二叉樹層次遍歷的實例圖。

    

 

4、二叉樹的線索化

二叉樹的線索化,起始就是利用二叉樹中的空的節點來將二叉樹轉換成鏈表的結構。固然只針對中序遍歷的序列。從上面中序遍歷的結果中,咱們不難看出,有節點的值與空指針是間隔的 D  B  E  A  C  F 空)。也就是說好多空的左指針與右指針浪費了。二叉樹的線索化,就是在中序遍歷中,將空的左子樹的指針指向其中序遍歷結果的前驅,而空的右子樹指針指向中序遍歷中該節點的後繼。具體的示意圖以下所示:

  

從上面的圖中咱們不難看出。在被線索化的二叉樹中,左節點指針不止指向左節點,並且有可能指向節點的前驅。而右節點指針不只僅是指向右節點的指針,還有可能指向該節點在中序遍歷中的後繼節點。爲了標記指針是指向子節點仍是指向前驅或者後繼,因此咱們要添加相應的標誌位來標記指針指向的是那些節點。下方就是咱們改造後的二叉樹的節點:

  

改造完節點後,咱們就能夠將二叉樹進行線索化了,下方就是被線索話的二叉樹的代碼。能夠看出,下方的代碼的總體步驟與二叉樹的中序遍歷相似。

  

被線索化的二叉樹就能夠根據咱們添加的線索進行中序遍歷了,效率要比遞歸的中序遍歷要高的多,以下所示:

  

 

5、測試用例

上面的代碼都是如何去實現了,接下來到了咱們測試的時間了,下方這段代碼段是咱們的測試用例。首先給出二叉樹的節點信息,而後先序的建立一棵二叉樹。而後給出二叉樹的先、中、後續遍歷,最後給出二叉樹線索話的結果。

   

下方截圖就是咱們測試用例的運行結果,一目瞭然,在此就不作過多的贅述了。

  

本篇博客的篇幅也夠長的了,就先到這兒吧,上述實例的完整Demo會在github上進行分享, 下篇博客咱們將要介紹圖的鄰接鏈表和鄰接矩陣,以及圖的BFS和DFS。

github連接地址:https://github.com/lizelu/DataStruct-Swift/tree/master/BinaryTree

相關文章
相關標籤/搜索