概述
中序遍歷,是二叉樹遍歷的一種。在二叉樹中,中序遍歷首先遍歷左子樹,然後遍歷根節點,最後遍歷右子樹。
遞歸遍歷法
public List<Integer> getLDRMethod1(TreeNode treeNode) {
List<Integer> result = new ArrayList<>();
if (treeNode != null) {
result.addAll(getLDRMethod1(treeNode.left));
result.add(treeNode.val);
result.addAll(getLDRMethod1(treeNode.right));
}
return result;
}
遞歸遍歷法就像概述中二叉樹定義的一種實現,首先遍歷左子樹,然後遍歷根節點,最後遍歷右子樹。這種方法一般用來求中序遍歷結果,因爲遞歸遍歷過程中,方法參數過多本身也是一種資源浪費,而只傳遞節點的話又不好計算各種累加問題,因此該方法在算法中,能用到場景較少。如果一定要用,也可以通過全局變量的方式來完成。
迭代法
public List<Integer> getLDRMethod(TreeNode treeNode) {
List<Integer> result = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
while (!stack.isEmpty() || treeNode != null) {
while (treeNode != null) {
stack.add(treeNode);
treeNode = treeNode.left;
}
treeNode = stack.pop();
result.add(treeNode.val);
treeNode = treeNode.right;
}
return result;
}
該迭代方法需要使用棧來保存還未遍歷的節點,遍歷思路也比較簡單,一直向左遍歷,模擬先遍歷左子樹這一步。如果左爲空,就記錄該節點值,模擬遍歷根節點這一步,然後向右遍歷,模擬遍歷右子樹這一步,一直循環到遍歷完所有節點。該方法在算法中經常使用,無論是計算累加問題,還是判斷問題,相比遞歸方法更高效和容易理解,也不需要使用全局變量,唯一的缺點就是需要引入新的棧空間,記錄還沒有遍歷的節點。
Morris中序遍歷
public List<Integer> getLDRMethod3(TreeNode treeNode) {
List<Integer> result = new ArrayList<>();
TreeNode predecessor = null;
while (treeNode != null) {
if (treeNode.left != null) {
predecessor = treeNode.left;
while (predecessor.right != null && predecessor.right != treeNode) {
predecessor = predecessor.right;
}
if (predecessor.right == null) {
predecessor.right = treeNode;
treeNode = treeNode.left;
} else {
predecessor.right = null;
result.add(treeNode.val);
treeNode = treeNode.right;
}
} else {
result.add(treeNode.val);
treeNode = treeNode.right;
}
}
return result;
}
看名字就知道該方法是一個比較難理解的方法。難理解的方法又是相對比較高效的方法:Morris遍歷是一個不需要使用棧,僅僅使用一個局部變量就可以完成中序遍歷的高效方法。在講解代碼之前,我先簡單介紹Morris中序遍歷的大致解決理念:
- Morris方法使每個節點的右指針都指向中序遍歷的下一個節點
- 有了理念1,我們也可以說,每次指向right的時候,就是遍歷的時候
那麼它具體是怎麼做的呢:
- 假設根節點A的左子樹B沒有右子樹,那麼B的右子樹就是A。
- 假設根節點A的左子樹B有右子樹,那麼就一直向右遍歷,直到它沒有右子樹,設置它的右子樹爲A
上述方案確定A的左子樹中序遍歷最後一個節點 的 right 指向它自己,也就是保證左子樹遍歷完成後,遍歷根節點。左子樹遍歷完有兩種情況:
- 左子樹爲空,說明這個節點沒有左子樹,當然也可以理解爲左子樹已經遍歷完了
- 左子樹的右節點遞歸最終指向自己,說明它的左子樹已經遍歷完成了,同時刪掉這個右指針,維護樹結構。
帶着這些結論我們來解釋上述方法的工作原理:
- 如果左子樹爲空,直接遍歷該節點,進入右子樹。
- 如果左子樹不爲空,取它的左子樹節點,遞歸該節點的右指針,直到爲空
- 設置該節點的右指針指向父節點,表示父節點的左子樹遍歷完成後,最後一個幾點的right指向它。
- 如果該節點的右指針已經指向父節點了,說明該父節點的左子樹已經遍歷完了,刪除這個新加的right標記,遍歷該節點,進入右子樹