二叉樹的前序、中序、後序的遞迴和迭代實現
前言
二叉樹遍歷常用的一般分為前序、中序、後序三種,下面使用遞迴和迭代兩種方法來實現這三種遍歷,這三種遍歷的順序分別為:
- 前序:中左右
- 中序:左中右
- 後序:左右中
記得時候就是左右不變,中跟著遍歷的方式走
遞迴
每次寫遞迴,都按照這三要素來寫,可以保證大家寫出正確的遞迴演算法!
-
確定遞迴函式的引數和返回值:
確定哪些引數是遞迴的過程中需要處理的,那麼就在遞迴函式里加上這個引數, 並且還要明確每次遞迴的返回值是什麼進而確定遞迴函式的返回型別。 -
確定終止條件:
寫完了遞迴演算法, 執行的時候,經常會遇到棧溢位的錯誤,就是沒寫終止條件或者終止條件寫的不對,作業系統也是用一個棧的結構來儲存每一層遞迴的資訊,如果遞迴沒有終止,作業系統的記憶體棧必然就會溢位。 -
確定單層遞迴的邏輯:
確定每一層遞迴需要處理的資訊。在這裡也就會重複呼叫自己來實現遞迴的過程。
所以以前序遍歷為例看上面三要素:
-
確定遞迴函式的引數和返回值:
因為要列印出前序遍歷節點的數值,所以需要傳入list存放返回結果
void traversal(TreeNode root, List<Integer> ret)
-
確定終止條件:
如果當前遍歷的這個節點是空,就直接return
if (root == null) { return; }
-
確定單層遞迴的邏輯:
前序遍歷是中左右,所以在單層遞迴的時候,需要先取中間節點的值
ret.add(root.val); // 中 traversal(root.left, ret); // 左 traversal(root.right, ret); // 右
所以根據上面的分析,三種遍歷的遞迴方式就不難寫了,程式碼如下:
/**
* 遍歷
*/
public List<Integer> traversal(TreeNode root) {
List<Integer> ret = new ArrayList<>();
// preOrderTraversal(root, ret);
// postOrderTraversal(root, ret);
return ret;
}
/**
* 前序
*/
private void preOrderTraversal(TreeNode root, List<Integer> ret) {
if (root == null) {
return;
}
ret.add(root.val);
traversal(root.left, ret);
traversal(root.right, ret);
}
/**
* 中序
*/
private void preOrderTraversal(TreeNode root, List<Integer> ret) {
if (root == null) {
return;
}
traversal(root.left, ret);
ret.add(root.val);
traversal(root.right, ret);
}
/**
* 後序
*/
private void postOrderTraversal(TreeNode root, List<Integer> ret) {
if (root == null) {
return;
}
traversal(root.left, ret);
traversal(root.right, ret);
ret.add(root.val);
}
迭代
前序遍歷
前序遍歷是中左右,每次先處理的是中間節點,那麼先將跟節點放入棧中,然後將右孩子加入棧,再加入左孩子。
為什麼要先加入 右孩子,再加入左孩子呢? 因為這樣出棧的時候才是中左右的順序。程式碼如下:
// 前序迭代 按照 中左右 的順序依次訪問節點,並將資料處理入陣列中
private List<Integer> preOrderTraversal(TreeNode root) {
Stack<TreeNode> stack = new Stack<TreeNode>();
List<Integer> ret = new ArrayList<Integer>();
if (root == null) {
return ret;
}
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
ret.add(node.val); // 中
if (node.right != null) {
stack.push(node.right); // 右 // 要先加入右孩子,出棧的時候才能先出做孩子
}
if (node.left != null) {
stack.push(node.left); // 左
}
}
return ret;
}
後序遍歷
先序遍歷是中左右,後續遍歷是左右中,那麼我們只需要調整一下先序遍歷的程式碼順序,就變成中右左的遍歷順序,然後在反轉result陣列,輸出的結果順序就是左右中了,程式碼如下:
/**
* 利用先序遍歷的方式來簡化
* 後序:左右中 <= 反轉 <= 中右左 <= 換左和右的順序 <= 中左右 : 前序
*/
private List<Integer> postOrderTraversal(TreeNode root) {
Stack<TreeNode> stack = new Stack<TreeNode>();
List<Integer> ret = new ArrayList<Integer>();
if (root == null) {
return ret;
}
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
ret.add(node.val); // 中
if (node.left != null) {
stack.push(node.left); // 左
}
if (node.right != null) {
stack.push(node.right); // 右
}
}
Collections.reverse(ret); // 將結果反轉之後就是左右中的順序了
return ret;
}
中序遍歷
在前序和後序遍歷的迭代中,有兩個比較關鍵的操作:
- 處理:將元素放進result陣列中
- 訪問:遍歷節點
分析一下為什麼剛剛寫的前序遍歷的程式碼,不能和中序遍歷通用呢,因為前序遍歷的順序是中左右,先訪問的元素是中間節點,要處理的元素也是中間節點,所以剛剛才能寫出相對簡潔的程式碼,因為要訪問的元素和要處理的元素順序是一致的,都是中間節點。
那麼再看看中序遍歷,中序遍歷是左中右,先訪問的是二叉樹頂部的節點,然後一層一層向下訪問,直到到達樹左面的最底部,再開始處理節點(也就是在把節點的數值放進result陣列中),這就造成了處理順序和訪問順序是不一致的。
那麼在使用迭代法寫中序遍歷,就需要借用指標的遍歷來幫助訪問節點,棧則用來處理節點上的元素。
/**
* 用指標的遍歷來幫助訪問節點,棧則用來處理節點上的元素
* 中序:左中右,所以堅持的原則就是左子樹存在就先處理左子樹
*/
private List<Integer> inOrderTraversal(TreeNode root) {
List<Integer> ret = new ArrayList<Integer>();
Stack<TreeNode> stack = new Stack<TreeNode>();
TreeNode cur = root; // 指標,標識訪問節點
while (cur != null || !stack.isEmpty()) {
if (cur != null) { // 指標來訪問節點,訪問到最底層
stack.push(cur); // 將訪問的節點放進棧
cur = cur.left; // 左
} else {
cur = stack.pop(); // 從棧裡彈出的資料,就是要處理的資料(放進result陣列裡的資料)
ret.add(cur.val); // 中
cur = cur.right; // 右
}
}
return ret;
}
附言
上文解釋了迭代實現的時候中序比較特殊的地方,原文中給出了三種迭代方式如何實現歸一化的實現方法,主要的思想就是解決上文說的訪問節點和處理節點不一致的情況,思路是將訪問的節點放入棧中,把要處理的節點也放入棧中但是要做個標記,當遇到這個標記的時候表示下一個節點時一個處理節點,可以放進結果集,不過個人覺得沒上文直接寫的直觀,有興趣的可以直接到原文中檢視
轉自這裡,點原文檢視
相關Leetcode
相關文章
- 二叉樹迭代器(中序遞迴、前序和後序遍歷)演算法二叉樹遞迴演算法
- 遞迴和迭代實現二叉樹先序、中序、後序和層序遍歷遞迴二叉樹
- 二叉樹 前序、中序、後序二叉樹
- 【根據前序和中序遍歷構造二叉樹】棧+迭代 || 遞迴二叉樹遞迴
- Java中用遞迴和迭代實現二叉樹的中序( InOrder )遍歷Java遞迴二叉樹
- 【二叉樹】前中序求後序,中後序求前序二叉樹
- 【演算法】二叉樹、N叉樹先序、中序、後序、BFS、DFS遍歷的遞迴和迭代實現記錄(Java版)演算法二叉樹遞迴Java
- [資料結構]二叉樹的前中後序遍歷(遞迴+迭代實現)資料結構二叉樹遞迴
- 144. 二叉樹的遍歷「前序、中序、後序」 Golang實現二叉樹Golang
- 二叉樹的前序、中序、後序三種遍歷二叉樹
- 非遞迴遍歷二叉樹的四種策略-先序、中序、後序和層序遞迴二叉樹
- 二叉樹的前中後序遍歷(遞迴和非遞迴版本)二叉樹遞迴
- [java] 二叉樹的後序遍歷(遞迴與非遞迴實現)Java二叉樹遞迴
- 二叉樹 ---- 前序 中序 後序 知二求一二叉樹
- 二叉樹的前序,中序,後序遍歷方法總結二叉樹
- 二叉樹的建立、前序遍歷、中序遍歷、後序遍歷二叉樹
- PHP基於非遞迴演算法實現先序、中序及後序遍歷二叉樹操作示例PHP遞迴演算法二叉樹
- 演算法 -- 實現二叉樹先序,中序和後序遍歷演算法二叉樹
- 【每日一題】二叉樹的前中後序非遞迴整理每日一題二叉樹遞迴
- 刷題筆記:樹的前序、中序、後序遍歷筆記
- 二叉樹建立,前序遍歷,中序遍歷,後序遍歷 思路二叉樹
- 二叉樹——後序遍歷的遞迴與非遞迴演算法二叉樹遞迴演算法
- 【樹01】對二叉樹前序/中序/後序遍歷演算法的一些思考二叉樹演算法
- 二叉樹先知道後序和中序,求先序二叉樹
- 已知二叉樹的先序和後序求任意一中序二叉樹
- 從前序與中序構造二叉樹二叉樹
- 【資料結構與演算法】二叉樹的 Morris 遍歷(前序、中序、後序)資料結構演算法二叉樹
- 還原二叉樹(先序+中序-〉後序)二叉樹
- 144.二叉樹的前序遍歷145.二叉樹的後序遍歷 94.二叉樹的中序遍歷二叉樹
- 非遞迴實現先序遍歷和中序遍歷遞迴
- 後序+中序(前序+中序)重構樹,嚴格O(N)演算法演算法
- 二叉樹中序和後序遍歷表示式二叉樹
- 根據二叉樹的前序遍歷和中序遍歷輸出二叉樹;二叉樹
- 二叉樹前序、中序、後序遍歷相互求法(code留著看,概念先看了)二叉樹
- 94. 二叉樹的中序遍歷(迭代)二叉樹
- 根據二叉樹的先序序列和中序序列還原二叉樹並列印後序序列二叉樹
- 非遞迴先序遍歷二叉樹遞迴二叉樹
- 二叉樹的四種遍歷方法:先序,中序,後序,層序二叉樹