數據結構與算法筆記-數據結構-二叉樹

[toc]

數據結構與算法筆記-數據結構-二叉樹

樹(Tree)

樹結構基本上就是和現實中的樹外觀看起來差不多,每個元素叫做節點,相鄰節點的稱之爲父子關係.

img

樹結構的節點關係如下圖

  • A節點就是B節點的父節點

  • B節點是A節點的子節點

  • B,C,D這三個節點的父節點是同一個節點,所以它們之間互稱爲兄弟節點

  • 沒有父節點的節點的爲根節點,也就是圖中的節點E

  • 沒有子節點的節點爲葉子節點或者葉節點,比如圖中的G,H,I,J,K,L都是葉子節點

img

樹結構有三個相似的概念: 高度(Height),深度(Depth),層(Level)

img

高度(Height),深度(Depth),層(Level),如圖

img

二叉樹(Binary Tree)

樹結構多種多樣,常用的是二叉樹,顧名思義二叉樹最多有兩個叉.

二叉樹,每個節點最多有兩個子節點

二叉樹,不要求每個節點必須有兩個子節點

二叉樹如下圖:

img

上圖中比較特殊的2號和3號

2號的葉子節點都在最底一層,除了葉子節點,每個節點都有左右兩個子節點, 這就是滿二叉樹

3號的葉子節點都在最底兩層,最後一層的葉子都靠左排列,且除了最後一層,其他層的節點個數都要達到最大,這就是完全二叉樹

img

存儲二叉樹

存儲方式一般有兩種

  1. 基於指針的二叉鏈式存儲法,最常用也是最簡單的,大部分二叉樹代碼都是通過這種結構實現的.

  2. 基於數組的順序存儲法,最好只用來存儲完全二叉樹,否則本來是可以節省內存的結果造成更多的浪費.

下圖二叉鏈式存儲法:

img

每個節點有三個字段一個存數據,另外外兩個是指向左右子節點的指針

注意,並不是說,只能有三個字段,也可以是四個字段,多出了一個字段作爲指向父節點的指針.

下圖基於數組的順序存儲法的完全二叉樹:

img

  1. 把根節點放在下標爲1的位置,設i=1
  2. 左子節點的下標爲2*i的位置,也就是2.
  3. 右子節點的下標爲2*i+1的位置,也就是3
  4. 以此類推,如果求B節點的子節點的話i=2
  5. B節點的左子節點應該是2*i也就是4
  6. B節點的右子節點應該是2*i+1也就是5的位置

如果節點X存儲在下標爲i的位置,那節點X的左子節點就是2*i,右子節點爲2*i+1.反過來i/2也就是他的父節點.

只要知道根節點的存儲位置,就可以通過計算下表拿到整個樹.

下圖基於數組的順序存儲法的非完全二叉樹:

img

完全二叉樹只不過浪費了數組中下標爲0的一個位置, 而非完全二叉樹會浪費比較多的數組存儲空間.

完全二叉樹, 用數組存儲是最節省內存的, 因爲數組的存儲方式並不需要像鏈式存儲法要存儲額外的左右子節點的指針.

也就是爲啥完全二叉樹要最後一層的子節點都靠左的原因

堆其實也是一種完全二叉樹,最常用的存儲方式就是數組.

遍歷二叉樹

一般有三種方式:

前序遍歷, 中序遍歷, 後序遍歷;

而前,中,後,表示節點和左右子樹節點遍歷打印的先後順序

  • 前序遍歷,對於樹中的任意節點來說,先打印該節點,然後再打印左子樹,最後打印右子樹.
  • 中序遍歷,對於樹中的任意節點來說,先打印左子樹,然後再打印該節點,最後打印右子樹.
  • 後序遍歷,對於樹中的任意節點來說,先打印左子樹,然後再打印右子樹,最後打印該節點.

img

二叉樹的前,中,後序遍歷其實是一個遞歸的過程.

比如,前序遍歷:

  1. 先打印根節點
  2. 然後再遞歸地打印左子樹
  3. 最後遞歸地打印右子樹

寫遞歸最重要的是推出遞推公式,推公式最重要的是解決問題A,解決問題A要假設B和C已解決,然後在看如何用B和C來解決A.

所以這裏可以把,前,中,後序遍歷的遞推公式都寫出來.

前序遍歷的遞推公式:
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

通過遞推公式寫三種遍歷方式的代碼

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節點
}

從之前的前,中,後序遍歷的順序圖可以看到每個節點最多會被訪問兩次,

所以遍歷操作的時間複雜度跟節點的個數n成正比,那麼二叉樹遍歷的時間複雜度是O(n).

小結

樹是非線性表數據結構, 常用的概念: 根節點,葉子節點,父節點,子節點,兄弟節點,節點的高度,深度,層數,和樹的高度.

二叉樹是樹中最常用的,二叉樹每個節點最多兩個子節點,爲左子節點和右子節點.

二叉樹中有兩種特殊樹,爲滿二叉樹和完全二叉樹, 滿二叉樹又是完全二叉樹的一種特殊情況。

二叉樹既可以用鏈式存儲,也可以用數組順序存儲.

但是數組順序存儲比較適合完全二叉樹,其他類型的二叉樹用數組存儲會比較浪費存儲空間.

二叉樹最重要的操作就是前,中,後序遍歷,遍歷的時間複雜度是O(n),需要理解並能用遞歸代碼來實現。

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