瞭解二叉樹的定義以及二叉樹遍歷之後,我們繼續探討二叉樹的使用。二叉樹是一種資料結構,是用來處理資料的。資料處理最簡單的需求是查詢、新增、刪除,有一種二叉查詢樹可以滿足以上需求。
二叉查詢樹也可以稱之為有序二叉樹。二叉查詢樹具有如下特性:
對於樹中的每個節點n,其左子樹中的值都小於節點n中的值,其右子樹中的值都大於節點n中的值。
就像這樣:
- 查詢
二叉查詢樹中查詢某個元素的演算法相當直觀。對於每個節點,演算法將要定位的值與當前所指節點中儲存的值進行比較,如果該值小於儲存值則轉向左子樹;如果該值大於儲存值,則轉向右子樹。如果兩者相同,很明顯查詢過程結束了。如果沒有其他可以查詢的節點,查詢過程也終止,這表示該值不在樹中。舉個例子,在上圖中查詢22。從根節點開始,首先22大於根節點15,轉向右子樹,發現23大於查詢節點22,隨後轉向左子樹,發現22等於查詢節點22,查詢完畢。
動態圖如下:
- 新增
要插入鍵值為el的新節點,必須找到樹中的一個終端節點,並將新節點與該節點連線。要找到這樣一個終端節點,可以使用與查詢樹相同的技術:在掃描樹的過程中,比較鍵值el與當前檢查的節點的鍵值。如果el小於該鍵值,就測試當前節點的左子樹,否則,就測試當前節點的右子樹。如果要測試的p的子節點為空,就停止掃描,新節點將成為p的子節點。舉個例子,在上圖中插入24。從根節點開始,首先24大於根節點15,轉向右子樹,發現23小於插入節點24,轉向右子樹,發現26大於插入節點24,轉向左子樹,發現為空,將24新增為26節點的左子樹,插入完畢。
動態圖如下:
- 刪除
另一個維護二叉查詢樹所必不可少的操作是刪除節點。執行該操作的複雜度取決於要刪除的節點在樹中的位置。刪除有兩個子樹的節點比刪除葉子節點困難得多,刪除演算法的複雜度與被刪除節點的子節點數目成正比。從二叉查詢樹中刪除節點有三種情況:
1、刪除葉節點
刪除葉節點比較簡單,因為葉節點沒有子樹,只要將父節點的相應指標設定為空即可。
動態圖如下:
2、刪除度為1的節點
刪除度為1的節點也不復雜,只要將父節點中指向該節點的指標重新設定為指向被刪除節點的子節點。這樣,被刪除節點的子節點提升一個層次,其後的所有後裔節點根據家族關係依次提升一個層次。
動態圖如下:
3、刪除度2節點
要刪除的節點有兩個子節點。在這種情況下,無法一步完成刪除操作,因為父節點的右指標或者左指標不能同時指向被刪除節點的兩個子節點。下面討論這一問題的兩種不同解決方案。
- 合併刪除
該解決方案從被刪除節點的兩顆子樹中得到一棵樹,然後將這顆樹連線到被刪除節點的父節點處。這種技術稱為合併刪除。但如何合併這些子樹呢?根據二叉查詢樹的本質,右子樹的每個值都比左子樹的值大,所以最好的方法是找到左子樹中具有最大值的節點,使它成為右子樹的父節點。同樣,還可以在右子樹中找到具有最小值的節點,使之成為左子樹的父節點。
動態圖如下:
上圖演示的是將右子樹合併到左子樹中成為一顆新的子樹,然後將被刪除節點的父節點重新指向子樹。當然也可以將左子樹合併到右子樹,動態圖如下:
刪除有兩個子樹的節點比較麻煩,原因在於兩個子樹怎麼處理?上面介紹的合併刪除是一種解決方案,就是說將兩顆子樹合併成一顆新樹,該樹依然滿足二叉查詢樹定義,然後將新樹提升到刪除節點的位置。合併刪除關鍵邏輯是如何合併子樹,如果我們決定將右子樹掛在左子樹上,那麼掛在哪個節點比較合適呢?我們假設刪除節點為p,p的左子樹為m,右子樹為n。那麼,n中所有節點大於p,p大於m中所有節點。我們再假設m的根節點為m1,n的根節點為n1,那麼n1比m中所有節點都大。也就是說,如果將n掛在m下,那麼從m1到n1的路徑中不能出現左指標,因為一旦出現左指標,就代表著n1成了一個 比它還小的節點的左子樹,這就破壞了二叉查詢樹。因此,只要m1到n1的路徑中沒有左指標即可,最方便的做法就是使n1成為m樹中最大節點的右子樹。這正是上面動圖中展示的內容。將左子樹合併到右子樹也是同理。看到這裡,也許讀者會產生一個疑問。假設m樹中最大節點為m2,從m1到m2的路徑中存在節點m3,那麼使n1成為m3的右子樹可以嗎?這當然是可以的,但是如果這樣做,就需要處理m3原有的右子樹了。方法就是將m3原有右子樹掛在n樹上,要求是從n1到m3原有右子樹根節點的路徑中不能存在右指標,原理和上面類似。你會發現這樣太麻煩了,應該直接將n樹掛在m2下的,這樣m2原來就沒有右子樹,所以就省去了後面的步驟。
合併刪除是一種優秀的刪除解決方案,但是它有一個缺陷,因為使用合併刪除之後,會使二叉查詢樹的高度變大,經過多次的合併刪除後,二叉查詢樹會退化為連結串列,這當然是不能容忍的。
為了規避合併刪除的缺陷,還有一種叫做複製刪除的解決方案。
- 複製刪除
複製刪除設計的非常巧妙。對於刪除,我們直觀的邏輯是刪除某個節點,然後將該節點的子節點通過某種調整,然後提升到被刪除節點的位置。複製刪除不是這種邏輯,複製刪除主張在被刪除節點的兩顆子樹中,找到某個節點來替換被刪除節點,這樣就規避了提升子樹的邏輯,並且不會破壞二叉查詢樹的高度。現在問題來了,被刪除節點的兩顆子樹中,哪些節點可以用來替換被刪除節點呢?答案就是被刪除節點的直接前驅或者直接後繼,直接前驅是其左子樹中最右側的節點(與此類似,其直接後繼是右子樹中最左側的節點)。你會發現,直接前驅是左子樹中最大的值,直接後繼是右子樹中最小的值,只有這兩個節點可以替換被刪除節點。你也許會問,為什麼左右子樹中其他節點不可以呢?因為如果找了其他節點p來替換被刪除節點,那麼就無法保證p大於左子樹中所有節點,或者p小於右子樹中所有節點,這就破壞了二叉查詢樹。因此,我們可以選擇被刪除節點的直接前驅或者直接後繼來替換該節點,而我們知道,直接前驅或者直接後繼要麼是葉子節點,要麼是隻有一個子樹。那麼問題就變得簡單了。使用直接前驅或者直接後繼替換被刪除節點之後,我們再把直接前驅或者直接後繼刪除掉,刪除它們的邏輯比較簡單,就是刪除葉子節點或者刪除度1節點,這在上面已經介紹過了。
動態圖如下:
二叉查詢樹的查詢、新增、刪除邏輯到目前為止已經探討完畢,更加深入的內容需要實踐中摸索。下一節一起學習二叉樹的平衡。