基本概念
- 結點:表示樹中的元素。
- 結點的度:擁有子結點的個數
- 葉子:度爲0的結點,也叫終端結點
- 樹的度:樹中結點的最大的度
- 結點的層次:根結點是第一層,它的孩子結點是第二層,依次類推。
- 樹的高度:最大層次數。
- 二叉樹:所有結點的度數不超過2的樹。
- 滿二叉樹:高度爲h的二叉樹恰好有2^h-1個結點時。
- 完全二叉樹:葉子結點只可能出現在最後一層或倒數第二層,每個非葉子結點要麼有兩個孩子結點,要麼只有左孩子結點。
滿二叉樹
葉子節點全都在最底層,除了葉子節點之外,每個節點都有左右兩個子節點
完全二叉樹
葉子節點都在最底下兩層,最後一層的葉子節點都靠左排列,並且除了最後一層,其他層的節點個數都要達到最大
2是滿二叉樹,3是完全二叉樹
二叉樹的存儲
要理解完全二叉樹定義的由來,需要先了解一棵二叉樹的存儲
想要存儲一棵二叉樹,有兩種方法,一種是基於指針或者引用的二叉鏈式存儲法,一種是基於數組的順序存儲法。
鏈式存儲法
圖中應該可以很清楚地看到,每個節點有三個字段,其中一個存儲數據,另外兩個是指向左右子節點的指針。我們只要拎住根節點,就可以通過左右子節點的指針,把整棵樹都串起來。
這種存儲方式比較常用。大部分二叉樹代碼都是通過這種結構來實現的
基於數組的順序存儲法
把根節點存儲在下標i = 1的位置,那左子節點存儲在下標2 * i = 2
的位置,右子節點存儲在2 * i + 1 = 3
的位置。
以此類推,B節點的左子節點存儲在2 * i = 2 * 2 = 4的位置,右子節點存儲在2 * i + 1 = 2 * 2 + 1 = 5的位置
如果節點X存儲在數組中下標爲i的位置,下標爲2 * i 的位置存儲的就是左子節點,下標爲2 * i + 1的位置存儲的就是右子節點。
反過來,下標爲i/2的位置存儲就是它的父節點。通過這種方式,我們只要知道根節點存儲的位置(一般情況下,爲了方便計算子節點,根節點會存儲在下標爲1的位置),這樣就可以通過下標計算,把整棵樹都串起來
不過,剛剛舉的例子是一棵完全二叉樹,所以僅僅“浪費”了一個下標爲0的存儲位置。
如果是非完全二叉樹,其實會浪費比較多的數組存儲空間。你可以看我舉的下面這個例子
所以,如果某棵二叉樹是一棵完全二叉樹,那用數組存儲無疑是最節省內存的一種方式。因爲數組的存儲方式並不需要像鏈式存儲法那樣,要存儲額外的左右子節點的指針。這也是爲什麼完全二叉樹會單獨拎出來的原因,也是爲什麼完全二叉樹要求最後一層的子節點都靠左的原因。
二叉樹的遍歷
經典的方法有三種,前序遍歷、中序遍歷和後序遍歷。 其中,前、中、後序,表示的是節點與它的左右子樹節點遍歷打印的先後順序。
前序遍歷
對於樹中的任意節點來說,先打印這個節點,然後再打印它的左子樹,最後打印它的右子樹。(本左右)
中序遍歷
對於樹中的任意節點來說,先打印它的左子樹,然後再打印它本身,最後打印它的右子樹。(左本右 有序輸出)
後序遍歷
對於樹中的任意節點來說,先打印它的左子樹,然後再打印它的右子樹,最後打印這個節點本身(左右本 左右都遍歷完了在操作本身,釋放二叉樹時使用 )
前序遍歷的遞推公式:
preOrder(r) = print r->preOrder(r->left)->preOrder(r->right)
中序遍歷的遞推公式:
inOrder(r) = inOrder(r->left)->print r->inOrder(r->right)
後序遍歷的遞推公式:
postOrder(r) = postOrder(r->left)->postOrder(r->right)->print r
1個節點有左右子樹,被分爲3個點
遞歸的訪問整棵樹的時候都要訪問這三個點