完全二叉樹
完全二叉樹是一種特殊的二叉樹,滿足以下要求:
-
所有葉子節點都出現在 k 或者 k-1 層,而且從 1 到 k-1 層必須達到最大節點數;
-
第 k 層可以不是滿的,但是第 k 層的所有節點必須集中在最左邊。
需要注意的是不要把完全二叉樹和“滿二叉樹”搞混了,完全二叉樹不要求所有樹都有左右子樹,但它要求: -
任何一個節點不能只有右子樹沒有左子樹
-
葉子節點出現在最后一層或者倒數第二層,不能再往上
用一張圖對比下“完全二叉樹”和“滿二叉樹”:
當我們用數組實現一個完全二叉樹時,葉子節點可以按從上到下、從左到右的順序依次添加到數組中,然后知道一個節點的位置,就可以輕松地算出它的父節點、孩子節點的位置。
以上面圖中完全二叉樹為例,標號為 2 的節點,它在數組中的位置也是 2,它的父節點就是 (k/2 = 1),它的孩子節點分別是 (2k=4) 和 (2k+1=5),別的節點也是類似。
完全二叉樹使用場景:
根據前面的學習,我們了解到完全二叉樹的特點是:“葉子節點的位置比較規律”。因此在對數據進行排序或者查找時可以用到它,比如堆排序就使用了它,后面學到了再詳細介紹。
二叉查找樹
二叉樹的提出其實主要就是為了提高查找效率,比如我們常用的 HashMap
在處理哈希沖突嚴重時,拉鏈過長導致查找效率降低,就引入了紅黑樹。
我們知道,二分查找可以縮短查找的時間,但是它要求 查找的數據必須是有序的。每次查找、操作時都要維護一個有序的數據集,於是有了二叉查找樹這個概念。
二叉查找樹(又叫二叉排序樹),它是具有下列性質的二叉樹:
-
若左子樹不空,則左子樹上所有結點的值均小於它的根結點的值;
-
若右子樹不空,則右子樹上所有結點的值均大於或等於它的根結點的值;
-
左、右子樹也分別為二叉排序樹。
如下圖所示:
也就是說,二叉查找樹中,左子樹都比節點小,右子樹都比節點大,遞歸定義。
根據二叉排序樹這個特點我們可以知道:二叉排序樹的中序遍歷一定是從小到大的。
比如上圖,中序遍歷結果是:
1 3 4 6 7 8 10 13 14
二叉排序樹的性能
在最好的情況下,二叉排序樹的查找效率比較高,是 O(logn),其訪問性能近似於折半查找;
但最差時候會是 O(n),比如插入的元素是有序的,生成的二叉排序樹就是一個鏈表,這種情況下,需要遍歷全部元素才行(見下圖 b)。
如果我們可以保證二叉排序樹不出現上面提到的極端情況(插入的元素是有序的,導致變成一個鏈表),就可以保證很高的效率了。
但這在插入有序的元素時不太好控制,按二叉排序樹的定義,我們無法判斷當前的樹是否需要調整。
因此就要用到平衡二叉樹(AVL 樹)了。
平衡二叉樹
平衡二叉樹的提出就是為了保證樹不至於太傾斜,盡量保證兩邊平衡。因此它的定義如下:
-
平衡二叉樹要么是一棵空樹
-
要么保證左右子樹的高度之差不大於 1
-
子樹也必須是一顆平衡二叉樹
也就是說,樹的兩個左子樹的高度差別不會太大。
那我們接着看前面的極端情況的二叉排序樹,現在用它來構造一棵平衡二叉樹。
以 12 為根節點,當添加 24 為它的右子樹后,根節點的左右子樹高度差為 1,這時還算平衡,這時再添加一個元素 28:
這時根節點 12 覺得不平衡了,我左孩子一個都沒有,右邊都有倆了,超過了之前說的最大為 1,不行,給我調整!
於是我們就需要調整當前的樹結構,讓它進行旋轉。
因為最后一個節點加到了右子樹的右子樹,就要想辦法給右子樹的左子樹加點料,因此需要逆時針旋轉,將 24 變成根節點,12 右旋成 24 的左子樹,就變成了這樣(有點丑哈哈):
這時又恢復了平衡,再添加 37 到 28 的右子樹,還算平衡:
這時如果再添加一個 30,它就需要在 37 的左子樹:
這時我們可以看到這個樹又不平衡了,以 24 為根節點的樹,明顯右邊太重,左邊太稀,想要保持平衡就 24 得讓位給 28,然后變成這樣:
丑了點,但的確保持了平衡。
依次類推,平衡二叉樹在添加和刪除時需要進行旋轉保持整個樹的平衡,內部做了這么復雜的工作后,我們在使用它時,插入、查找的時間復雜度都是 O(logn),性能已經相當好了。
總結
閱讀完這篇文章,相信你對“完全二叉樹、二叉查找樹和平衡二叉樹”這三個二叉樹有了更加深刻的認識,有了這個基礎,再去看 B+ B- 紅黑樹,就相對容易些了,后面有機會的話我再寫兩篇這種的。