18. 二叉樹(下):二叉查找樹與散列表的優缺點

二叉查找樹(Binary Search Tree)

二叉查找樹是二叉樹中最常用的一種類型,也叫二叉搜索樹。顧名思義,二叉查找樹是爲了實現快速查找而生的。不過,它不僅僅支持快速查找一個數據,還支持快速插入、刪除一個數據。它是怎麼做到這些的呢?

這些都依賴於二叉查找樹的特殊結構。二叉查找樹要求,在樹中的任意一個節點,其左子樹中的每個節點的值,都要小於這個節點的值,而右子樹節點的值都大於這個節點的值。 我畫了幾個二叉查找樹的例子,你一看應該就清楚了。

前面我們講到,二叉查找樹支持快速查找、插入、刪除操作,現在我們就依次來看下,這三個操作是如何實現的。

1.二叉查找樹的查找操作

首先,我們看如何在二叉查找樹中查找一個節點。我們先取根節點,如果它等於我們要查找的數據,那就返回。如果要查找的數據比根節點的值小,那就在左子樹中遞歸查找;如果要查找的數據比根節點的值大,那就在右子樹中遞歸查找。

2.二叉查找樹的插入操作

二叉查找樹的插入過程有點類似查找操作。新插入的數據一般都是在葉子節點上,所以我們只需要從根節點開始,依次比較要插入的數據和節點的大小關係。

如果要插入的數據比節點的數據大,並且節點的右子樹爲空,就將新數據直接插到右子節點的位置;如果不爲空,就再遞歸遍歷右子樹,查找插入位置。同理,如果要插入的數據比節點數值小,並且節點的左子樹爲空,就將新數據插入到左子節點的位置;如果不爲空,就再遞歸遍歷左子樹,查找插入位置。

3.二叉查找樹的刪除操作

二叉查找樹的查找、插入操作都比較簡單易懂,但是它的刪除操作就比較複雜了 。針對要刪除節點的子節點個數的不同,我們需要分三種情況來處理。

第一種情況是,如果要刪除的節點沒有子節點,我們只需要直接將父節點中,指向要刪除節點的指針置爲null。比如圖中的刪除節點55。

第二種情況是,如果要刪除的節點只有一個子節點(只有左子節點或者右子節點),我們只需要更新父節點中,指向要刪除節點的指針,讓它指向要刪除節點的子節點就可以了。比如圖中的刪除節點13。

第三種情況是,如果要刪除的節點有兩個子節點,這就比較複雜了。我們需要找到這個節點的右子樹中的最小節點,把它替換到要刪除的節點上。然後再刪除掉這個最小節點,因爲最小節點肯定沒有左子節點(如果有左子結點,那就不是最小節點了),所以,我們可以應用上面兩條規則來刪除這個最小節點。比如圖中的刪除節點18。

解答開篇

我們在散列表那節中講過,散列表的插入、刪除、查找操作的時間複雜度可以做到常量級的O(1),非常高效。而二叉查找樹在比較平衡的情況下,插入、刪除、查找操作時間複雜度纔是O(logn),相對散列表,好像並沒有什麼優勢,那我們爲什麼還要用二叉查找樹呢?

我認爲有下面幾個原因:

第一,散列表中的數據是無序存儲的,如果要輸出有序的數據,需要先進行排序。而對於二叉查找樹來說,我們只需要中序遍歷,就可以在O(n)的時間複雜度內,輸出有序的數據序列。

第二,散列表擴容耗時很多,而且當遇到散列衝突時,性能不穩定,儘管二叉查找樹的性能不穩定,但是在工程中,我們最常用的平衡二叉查找樹的性能非常穩定,時間複雜度穩定在O(logn)。

第三,籠統地來說,儘管散列表的查找等操作的時間複雜度是常量級的,但因爲哈希衝突的存在,這個常量不一定比logn小,所以實際的查找速度可能不一定比O(logn)快。加上哈希函數的耗時,也不一定就比平衡二叉查找樹的效率高。

第四,散列表的構造比二叉查找樹要複雜,需要考慮的東西很多。比如散列函數的設計、衝突解決辦法、擴容、縮容等。平衡二叉查找樹只需要考慮平衡性這一個問題,而且這個問題的解決方案比較成熟、固定。

最後,爲了避免過多的散列衝突,散列表裝載因子不能太大,特別是基於開放尋址法解決衝突的散列表,不然會浪費一定的存儲空間。

綜合這幾點,平衡二叉查找樹在某些方面還是優於散列表的,所以,這兩者的存在並不衝突。我們在實際的開發過程中,需要結合具體的需求來選擇使用哪一個。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章