二叉樹基礎上

seniusen發表於2018-11-14

1. 樹(Tree)

首先我們來看幾個樹的例子。

二叉樹基礎上

在一個樹結構裡,每個元素我們稱之為節點,從上到下相鄰節點連線的關係,我們稱之為父子關係

二叉樹基礎上

在上面的圖中,A 節點就是 B 節點的父節點,B 節點就是 A 節點的子節點。B、C、D 這三個節點的父節點是同一個節點,所以它們之間互稱為兄弟節點。另外,如果一個節點沒有父節點,我們稱之為根節點,比如上圖中的 E 節點。沒有子節點的節點稱為葉子節點或者葉節點,比如上圖中的 G、H、I、J、K、L 節點。

還有幾個關於樹的比較重要的概念:高度(Height)、深度(Depth)、層(Level),它們的定義如下。

二叉樹基礎上

高度其實就是從下往上計量,最下面的葉節點高度為 0,每向上一個節點,高度加 1。深度則是從上往下計量,根節點深度為 0,每向下一個節點,深度加 1。層則和深度類似,只不過根節點的層為 1,計數從 1 開始罷了。

二叉樹基礎上

2. 二叉樹(Binary Tree)

樹的結構多種多樣,但我們最常用的還是二叉樹。

二叉樹,顧名思義,就是說每個節點最多有兩個分叉,也就是兩個子節點,分別為左子節點和右子節點。不過,二叉樹並不要求每個結點都有兩個子節點,而是最多隻能有兩個子節點。以此類推,四叉樹、八叉樹也和二叉樹同理。

二叉樹基礎上

上圖中,第 2 個二叉樹中的葉子節點全都在最底層,而且,除了葉子節點外,每個節點都有左右兩個子節點,這種二叉樹就叫作滿二叉樹

第 3 個二叉樹中的葉子節點都在最底下兩層,最後一層的葉子節點都靠左排列,並且除了最後一層,其它層的節點個數都要達到最大,這種二叉樹就叫作完全二叉樹

二叉樹基礎上

完全二叉樹為什麼最後一層的葉子節點要靠左排列呢?這個定義有什麼由來或者說目的在哪裡呢?我們要從二叉樹的儲存方式來了解。想要儲存一棵二叉樹,我們有兩種方法,一種是基於指標或者引用的二叉鏈式儲存法,一種是基於陣列的順序儲存法。

我們先來看比較簡單、直觀的鏈式儲存法。其中,每個節點有三個欄位,其中一個儲存資料,另外兩個分別是指向左右子節點的指標。

二叉樹基礎上

再來看基於陣列的順序儲存法。我們把根節點儲存在下標為 i = 1 的位置,左子節點就儲存在下標為 2 * i = 2 的位置,右子節點就儲存在下標為 2 * i + 1 = 3 的位置。以此類推,B 節點的左子節點 就儲存在 2 * 2 = 4 的位置,右子節點就儲存在 2 * 2 + 1 = 5 的位置。

二叉樹基礎上

也就是說,如果節點 X 儲存在下標為 i 的位置,那下標為 2 * i 的位置儲存的就是左子節點,下標為 2 * i + 1 的位置儲存的就是右子節點,而且下標為 i / 2 的位置儲存的就是其父節點。以這種方式儲存,我們就可以通過根節點的位置很容易地把整棵樹都串起來。

不過,剛才的情況是一棵完全二叉樹,我們就浪費了陣列中一個下標為 0 的位置。如果是一個非完全二叉樹,那就會浪費比較多的陣列儲存空間。

二叉樹基礎上

所以,如果某棵樹是完全二叉樹,那用陣列來儲存無疑是最節省記憶體的方式,因為陣列不需要額外的指標來指向左右子節點。因此,這也就是為什麼完全二叉樹會要求最後一層的子節點都靠左的原因。

3. 二叉樹的遍歷

二叉樹的遍歷有三種經典方法,前序遍歷、中序遍歷和後序遍歷,其中,前、中、後序指的是節點和它的左右子樹節點列印的先後順序。

  • 前序遍歷,就是對於樹中的任意節點來說,先列印這個節點,然後再列印它的左子樹,最後列印它的右子樹。

  • 中序遍歷,就是對於樹中的任意節點來說,先列印它的左子樹,然後再列印這個節點,最後列印它的右子樹。

  • 後序遍歷,就是對於樹中的任意節點來說,先列印它的左子樹,然後再列印它的右子樹,最後列印這個節點。

二叉樹基礎上

可以看到,二叉樹的遍歷其實就是一個遞迴的過程。比如前序遍歷,其實就是先列印根節點,然後再遞迴地列印左子樹,最後遞迴地列印右子樹。

void preOrder(Node* root) {
  if (root == null) return;
  print root // 此處為虛擬碼,表示列印 root 節點
  preOrder(root->left);
  preOrder(root->right);
}

void inOrder(Node* root) {
  if (root == null) return;
  inOrder(root->left);
  print root // 此處為虛擬碼,表示列印 root 節點
  inOrder(root->right);
}

void postOrder(Node* root) {
  if (root == null) return;
  postOrder(root->left);
  postOrder(root->right);
  print root // 此處為虛擬碼,表示列印 root 節點
}
複製程式碼

在遍歷的過程中,每個節點最多會被訪問兩次,所以遍歷的時間複雜度與節點的個數成正比,為 O(n)。

參考資料-極客時間專欄《資料結構與演算法之美》

獲取更多精彩,請關注「seniusen」!

二叉樹基礎上

相關文章