老當機帶你用 PHP 實現資料結構之二叉樹

Dennis_Ritchie發表於2019-12-13

目的

根據我在這段時間的觀察,很多人不懂一些基本的資料結構,所以今天在這裡,我不準備講Laravel的相關知識,而是來一點兒資料結構知識的科普:二叉樹。程式碼我已經上傳到了碼雲php-tree,希望大家能夠下載下來。下面的講解都是根據程式碼來的,仔細閱讀。程式碼可能不是那麼容易理解,沒關係,我在這篇博文的後面會給大家仔細的講解。

二叉樹的起源

關於到底是誰發明了二叉樹,已經無從得知了,反正歷史十分悠久,之所以二叉樹會存在,是為了解決線性搜尋耗費時間的問題,但是因為二叉樹的極端情況(有可能演變為連結串列),所以二叉樹在生產環境下基本沒有使用,但是二叉樹衍生除了許多其它的變種,比如 紅黑樹(Red-Black-Tree), 紅黑樹的應用無處不在(作業系統,標準庫等等),但是要想理解這些變種,基本的二叉樹原理,你是必須要知道的。

二叉樹的定義

二叉樹的定義很簡單,如下:

  1. 每一個節點,有且最多隻有2個子節點。
  2. 每一個節點的左子節點的值小於當前節點的值,當前節點的值小於右子節點的值。

定義很簡單,沒錯,就是這麼簡單,希望大家記住。

二叉樹的操作

今天我要講的是二叉樹的插入和刪除操作,因為這是它的核心操作,請大家仔細理解。

程式碼結構

博文的開頭,我已經給出了程式碼的地址,下載是必須的,程式碼結構如下所示:

老當機帶你用PHP實現資料結構之二叉樹

老當機帶你用PHP實現資料結構之二叉樹

每個檔案的作用我已經給大家標出來了,這裡我還要給大家簡要的介紹一下沒各類的基本屬性。

二叉樹的每一個節點都是Node類,這個類很簡單,如下:

老當機帶你用PHP實現資料結構之二叉樹

各個屬性的含義,我都給大家標出來了,就不再說了,NodeComparator是一個介面,所有的結點比較器(如果比較兩個結點的大小)都應該實現NodeComparator結構,NodeComparator介面定義如下:

老當機帶你用PHP實現資料結構之二叉樹

最後是BinaryTree類了,這個類有點兒複雜,其它的操作在本篇博文的後面,我們在這裡只看它的基礎屬性:

老當機帶你用PHP實現資料結構之二叉樹

關於如何實現自己的結點比較器,我在test.php檔案中有給大家演示,下面我貼出來:

老當機帶你用PHP實現資料結構之二叉樹

上面簡要的介紹了基本類的一些作用,下面開始深入二叉樹的插入和刪除操作。

二叉樹的插入

在閱讀下面的程式碼前,我希望,你已經理解了二叉樹的定義,廢話不多說。

老當機帶你用PHP實現資料結構之二叉樹

因為插入操作不是那麼複雜,所以這裡直接貼出了所有的程式碼,
Node::create方法建立了一個Node類的物件,首先判斷根節點root_node是否為空,如果為空的話,那麼直接設定根節點就可以了,如果不為空的話,那麼就需要從根節點遍歷整棵樹,這裡提醒大家,記住二叉樹的定義,這裡所有的一切都是以這個為依據,BinaryTree的compare方法用於比較2個Node節點的大小,實現如下:

老當機帶你用PHP實現資料結構之二叉樹

這個很簡單,不多說,compare方法比較當前結點node和將要插入的結點new_node的大小值,如果node的值大於new_node的值,也就是說new_node會插入到node的左子樹中,此時我們判斷node是否存在左子節點,判斷方法就是node->getLeftNode方法,如果存在的話,我們直接把左子節點賦值給node變數,這樣下次迭代就是以左子節點為判斷依據,也就是說下次迭代的時候,node就是當前迭代node的左子節點,如果不存在的話,那麼也就是說new_node直接插入作為node節點的左子樹。如果當前節點小於new_node的值的話,也就是說new_node會插入到當前迭代節點的右子樹中,這裡首先判斷,當前節點的右子節點是否存在,如果不存在的話,new_node節點直接作為它的當前node節點的右子節點即可,如果存在的話,那麼我們就需要繼續迭代整棵二叉樹,只不過下次迭代的node節點為當前node節點的右子節點,如此反覆,直到找到滿足條件的節點。

二叉樹的刪除

不管是二叉樹,還是它的衍生樹,刪除操作都是極其麻煩的,因為情況實在是太多了,下面我們一一分析:

刪除操作位於BinaryTree的delete方法中,如下:

老當機帶你用PHP實現資料結構之二叉樹

在分析程式碼之前,我先給大家分析刪除的所有情況:

A. 二叉樹為空,那麼刪除操作,就直接執行完畢了

B. 被刪除的節點沒有子節點,也就是下圖所展示的情況

老當機帶你用PHP實現資料結構之二叉樹

如果一個節點沒有子節點,那麼這個節點要麼是根節點,要麼是葉子節點,分別如上圖所示的情況1和情況2,這種情況很簡單。

C.被刪除的節點有且只有一個子節點,此時的所有情況如下所示

老當機帶你用PHP實現資料結構之二叉樹

  1. 情況3:被刪除的節點是根節點,並且它的左子節點是存在的。
  2. 情況4:被刪除節點是根節點,並且它的右子節點是存在。
  3. 情況5:被刪除結點不是根節點,並且它的左子節點是存在的,同時被刪除節點是它父節點的右子節點。
  4. 情況6:被刪除結點不是根節點,並且它的左子節點是存在的,同時被刪除節點是它父節點的左子節點。
  5. 情況7:被刪除結點不是根節點,並且它的右子節點是存在的,同時被刪除節點是它父節點的左子節點。
  6. 情況8:被刪除結點不是根節點,並且它的右子節點是存在的,同時被刪除節點是它父節點的右子節點。

D:被刪除結點有2個子節點,此時,情況有點兒複雜,如下:

老當機帶你用PHP實現資料結構之二叉樹

  1. 情況9:被刪除節點是根節點,並且被刪除節點的右子節點沒有左子節點。
  2. 情況10:被刪除節點不是根節點,並且被刪除節點是父節點的右子節點。
  3. 情況11:被刪除節點不是根節點,並且被刪除節點是父節點的左子節點。
  4. 情況12:被刪除節點不是根節點,並且被刪除節點是父節點的右子節點,並且被刪除節點的右子節點有左子節點。
  5. 情況13:被刪除節點不是根節點,並且被刪除節點是父節點的左子節點,並且被刪除節點的右子節點有左子節點。
  6. 情況14:被刪除節點是根節點,並且被刪除節點的右子節點有左子節點。

上面我們分析了二叉樹刪除的所有情況,接下來我會根據分析情況對程式碼進行解析。

程式碼分析

我們現在分析的重點是Binarytree的delete方法,要執行刪除操作,必須先找到需要刪除的結點。

尋找刪除的結點

尋找刪除結點的過程和插入結點的過程是一樣的,只不過compare的結果為0才算是找到了目標刪除結點,簡化程式碼為:

老當機帶你用PHP實現資料結構之二叉樹

這部分程式碼和插入是一樣的,從根節點遍歷整棵樹,直到找到compare為0的結點,相信大家如果理解了之前所說的插入,那麼這裡也應該理解了。

刪除目標節點

刪除的程式碼,我們分幾個模組分析,首先被刪除結點沒有子節點

老當機帶你用PHP實現資料結構之二叉樹

下面是被刪除節點只有一個子節點
老當機帶你用PHP實現資料結構之二叉樹

接下來是被刪除結點存在2個子節點:

老當機帶你用PHP實現資料結構之二叉樹

程式碼的流程和上面的分析一一對應,如果你看懂了我上面的分析,程式碼肯定沒有問題,好了,測試檔案如下所示:

老當機帶你用PHP實現資料結構之二叉樹

總結

請原諒,因為在貼出程式碼之前,我已經列舉出了二叉樹刪除的所有情況,所以後面沒有再仔細的分析程式碼,你只要看懂我上面的例項圖片,看懂程式碼,完全不是問題,如果你遇到困難,請加qq群:

如果有不懂的地方,可以加我的qq:1174332406,或者是微信:itshardjs

相關文章