二叉樹的基礎---四種遍歷方式的 Java 實現

0. 前言

你們好,我是多選參數的程序鍋,一個正在「研究」操做系統、學數據結構和算法以及 Java 的硬核菜雞。本篇將帶來的是二叉樹的相關知識,知識提綱如圖所示。git

1. 基本介紹

樹結構多種多樣,可是最經常使用的仍是二叉樹。二叉樹中每一個節點最多有兩個子節點,這兩個節點分別是左子節點和右子節點。注意:不要求都有兩個子節點,能夠只有左子節點,也能夠只有右子節點。github


2. 二叉樹的存儲

2.1. 鏈式存儲法

每一個節點至少有三個字段,其中一個存儲數據,另外兩個是指向左右子節點的指針。這種存儲方式比較經常使用,大部分二叉樹代碼都是經過這種結構來實現的。web


2.2. 數組存儲法

咱們把根節點存儲在下標 i=1 的位置,它的左子節點存儲在下標爲 2 * i 的位置,右子節點存儲在下標爲 2*i+1 的位置。以此類推,B 節點、C 節點的左右子節點都按照這種規律進行存儲,最終以下圖所示。算法

綜上,若是節點 X 存儲在數組中下標爲 i 的位置,那麼下標爲 2*i 的位置存儲的就是它的左子節點,下標爲 2*i+1 的位置存儲的就是它的右子節點。反過來,i/2 的位置存儲的就是它的父節點。通常狀況下,爲了方便計算,根節點會被存儲在下標爲 1 的位置。數組


經過上述能夠看到,針對通常樹來講,使用數組的方式存儲樹會浪費比較多的存儲空間。可是針對下文會提到的滿二叉樹或者徹底二叉樹來講,數組存儲的方式是最節省內存的一種方式。由於數組存儲時,不須要再存儲額外的左右子節點的指針。微信

3. 二叉樹的遍歷

二叉樹的遍歷就是將二叉樹中的全部節點遍歷打印出來。經典的方法有三種,前序遍歷、中序遍歷和後序遍歷,還能夠按層遍歷(我的理解的按層遍歷其實就是按照圖的廣度優先遍歷方法來進行遍歷)。數據結構

前、中、後是根據節點被打印的前後來進行區分的:前序就是先打印節點自己,以後再打印它的左子樹,最後打印它的右子樹;中序就是先打印節點的左子樹,再打印節點自己,最後打印右子樹,即把節點放中間的位置輸出;後序就是先打印節點的左子樹,再打印節點的右子樹,最後打印節點自己。以下圖所示app


按層遍歷相似於圖的廣度優先遍歷,先打印第一層的節點,以後再依次打印第二層的節點,以此類推。數據結構和算法


3.1. 代碼實現

實際上,二叉樹的前、中、後序遍歷是一個遞歸的過程。好比,前序遍歷,其實就是先打印根節點,而後遞歸遍歷左子樹,最後遞歸遍歷右子樹。遞歸遍歷左右子樹其實就跟遍歷根節點的方法同樣。下面先寫出這三者遍歷的遞推公式:編輯器

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

以後將遞推公式轉化爲代碼以下所示:

/**
* 前序遍歷
*/

public void preOrder(Node tree) {
if (tree == null) {
return;
}

System.out.print(tree.data + " ");

preOrder(tree.left);

preOrder(tree.right);
}

/**
* 中序遍歷
*/

public void inOrder(Node tree) {
if (tree == null) {
return;
}

inOrder(tree.left);

System.out.print(tree.data + " ");

inOrder(tree.right);
}

/**
* 後序遍歷
*/

public void postOrder(Node tree) {
if (tree == null) {
return;
}

postOrder(tree.left);

postOrder(tree.right);

System.out.print(tree.data + " ");
}

遞歸代碼的關鍵,在於寫出遞推公式。而遞推公式的關鍵在於,A 問題能夠被拆解成 B、C 兩個問題。假設要解決 A 問題,那麼假設 B、C 問題已經解決了。那麼在 B、C 已經解決的提早下,看如何利用 B、C 來解決 A 。千萬不要模擬計算機一層一層想下去,不然你就會發現你本身都不知道在哪了。

下面是按層遍歷的代碼,按層遍歷須要用到隊列的入隊和出隊等操做。先將根節點放入到隊列中,而後循環從隊列中取節點(出隊),再將該節點的左右子節點入隊。出隊的順序就是層次遍歷的結果。

/**
* 層次遍歷
*/

public void BFSOrder(Node tree) {
if (tree == null) {
return;
}

Queue<Node> queue = new LinkedList<>();
Node temp = null;
queue.offer(tree);
while (!queue.isEmpty()) {
temp = queue.poll();
System.out.print(temp.data + " ");
if (temp.left != null) {
queue.offer(temp.left);
}

if (temp.right != null) {
queue.offer(temp.right);
}
}
}

完整的代碼可查看 github 倉庫 https://github.com/DawnGuoDev/algos ,這個倉庫將主要包含經常使用數據結構及其基本操做的手寫實現(Java),也會包含經常使用算法思想經典例題的實現(Java)。在程序鍋找到工做以前,這個倉庫將會保持更新狀態,在此之間學到的關於數據結構和算法的知識或者實現也都會往裏面 commit,因此趕忙來 star 哦。

3.2. 時間複雜度

遍歷過程當中的次數就是訪問全部節點的所需的次數,而每一個節點最多被訪問兩次,所以遍歷的時間複雜度是跟節點的個數 n 成正比的,即遍歷的時間複雜度是 O(n)。

4. 特殊的二叉樹

4.1. 滿二叉樹

滿二叉樹是一種特殊的二叉樹,並且仍是徹底二叉樹的一種特殊狀況。如上圖編號 2 的那棵樹所示,葉子節點全在底層,除了葉子節點以外,每一個節點都有左右兩個子節點。

4.2. 徹底二叉樹

徹底二叉樹也是一種特殊的二叉樹。如上圖編號 3 的那棵樹所示,葉子節點都在最底下兩層,最後一層的葉子節點都靠左排列,而且除了最後一層,其餘層的節點個數都達到最大。


徹底二叉樹的特徵使得它可使用數組就能夠很好地存儲數據。徹底二叉樹要求最後一層的葉子節點靠左排列也是由於如此。

4.2.1. 徹底二叉樹的存儲

  • 鏈式存儲

    就是上面提到的那種方式。

  • 數組存儲

    徹底二叉樹使用數組存儲時,以下圖所示。咱們發現使用數組來存儲徹底二叉樹是一種很節省內存的方式。這也是徹底二叉樹被做爲一種特殊樹的緣由,也是徹底二叉樹要求最後一層的子節點必須都靠左的緣由。

    在講解堆或者堆排序的時候,堆其實也是一種徹底二叉樹,最經常使用的存儲方式就是數組


4.3. 其餘特殊的二叉樹

其餘特殊的二叉樹還有二叉查找樹、平衡二叉查找樹等。由於這兩種特殊的樹涵蓋的知識比較多,因此會將其分開進行單獨講解。

5. 巨人的肩膀

  1. 極客時間專欄,王爭老師的《數據結構與算法之美》

6. 附 Github

整個系列的代碼可查看 github 倉庫 https://github.com/DawnGuoDev/algos ,這個倉庫將主要包含經常使用數據結構及其基本操做的手寫實現(Java),也會包含經常使用算法思想經典例題的實現(Java)。在接下來一年內,這個倉庫將會保持更新狀態,在此之間學到的關於數據結構和算法的知識或者實現也都會往裏面 commit,因此趕忙來 star 哦。


不甘於「本該如此」,多選參數 值得關注




本文分享自微信公衆號 - 多選參數(zhouxintalk)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索