高度:根節點的高度最高(從0開始算起)
深度:根節點的深度最淺(從0開始算起)
層:根節點的層數最小(從1開始算起)
二叉樹中有兩種比較特殊的二叉樹:
完全二叉樹:葉子節點都在最底下兩層,最後一層的葉子節點都靠左排列,並且除了最後一層,其他層的節點個數都要達到最大
滿二叉樹:葉子節點全都在最底層,除了葉子節點之外,每個節點都有左右兩 個子節點
二叉樹的存儲最直觀的是鏈式存儲法:
class Node{
int data;
Node left;
Node right;
}
基於數組的順序存儲法:
我們把根節點存儲在下標 i = 1 的位置,那左子節點存儲在下標 2 * i = 2 的位置,右子節點存儲在 2 * i + 1 = 3 的位置。以此類推,B 節點的左子節點存儲在 2 * i = 2 * 2 = 4 的位置,右子節點存儲在 2 * i + 1 = 2 * 2 + 1 = 5。根節點一般不存在下標爲0的位置,因爲這樣的話,左節點就是i2+1,右子節點存儲在i2+2的位置,計算的時候不方便計算,左右子樹都有一個加法。
基於數組的順序存儲方法存儲非完全二叉式的時候會浪費很多空間。這也是爲什麼完全
二叉樹會單獨拎出來的原因,也是爲什麼完全二叉樹要求最後一層的子節點都靠左的原因。
二叉樹的前、中、後序,表示的是節點與它的左右子樹節點遍歷打印的先後順序(根節點的打印時機)。
前序遍歷是指,對於樹中的任意節點來說,先打印這個節點,然後再打印它的左子樹,最後
打印它的右子樹。
中序遍歷是指,對於樹中的任意節點來說,先打印它的左子樹,然後再打印它本身,最後打
印它的右子樹。
後序遍歷是指,對於樹中的任意節點來說,先打印它的左子樹,然後再打印它的右子樹,最
後打印這個節點本身。
實際上,二叉樹的前、中、後序遍歷就是一個遞歸的過程。比如,前序遍歷,其實就是先打印根
節點,然後再遞歸地打印左子樹,最後遞歸地打印右子樹。
寫遞歸代碼的關鍵,就是看能不能寫出遞推公式,而寫遞推公式的關鍵就是,如果要解決問題
A,就假設子問題 B、C 已經解決,然後再來看如何利用 B、C 來解決 A。所以,我們可以把
前、中、後序遍歷的遞推公式都寫出來。
preOrder(Tree)=print(data)->preOrder(Tree.left)->preOrder(Tree.right)
inOrder(Tree)=inOrder(Tree.left)->print(data)->inOrder(Tree.right)
postOrder(Tree)=postOrder(Tree.left)->postOrder(Tree.right)->print(data)
從前面畫的前、中、後序遍歷的順序圖,可以看出來,每個節點最多會被訪問兩次,所以遍歷
操作的時間複雜度,跟節點的個數 n 成正比,也就是說二叉樹遍歷的時間複雜度是 O(n)。
特殊的二叉樹
特殊的的二叉樹,二叉查找樹。二叉查找樹最大的特點就是,支持動態數據集合的快速插入、刪除、查找操作。
極端情況下二叉樹會退化成爲鏈表。
所以說又出現了平衡二叉查找樹。
平衡二叉樹的嚴格定義是:二叉樹中的任何一個節點的左右子樹的高度相差不能大於1
所以說完全二叉樹一定是平衡二叉樹,非完全二叉樹也有可能是平衡樹。
再加上查找二字則是:在樹中的任意一個節點,其左子樹中的每個節點的值,都要小於這個節點的值,而右子樹節點的值都大於這個節點的值
平衡二叉查找樹的數據結構實現有很多種,如:
- AVL樹
- 紅黑樹(不滿足上面的平衡二叉樹的絕對定義)
- 伸展樹(Splay Tree),樹堆(Treap)等等。
由此又延展出了平衡多叉查找樹數據結構,如:
- B樹(B-樹)
- B+樹
- B*樹(B+樹的變種)
二叉樹增加,刪除,刪除的複雜度是的O(log n)
散列表增加,刪除,刪除的複雜度是的O(1)
但是散列表的底層是數組存儲的數據,所以涉及到擴容,縮容並不方便
在加上散列函數的設計,衝突解決辦法等,要考慮的東西很多。如果考慮上散列衝突,這個O(1)並不比常量小。再加上hash函數的耗時,效率並不一定很高。另外散列表中的數據是無序的,所以排列輸出比較不方便。