這篇文章的目的只是整理幾個常用的二叉樹遞歸/非遞歸遍歷的模板函數,方便用到的時候憑藉記憶寫出來即可。內容沒多少技術含量,寫給自己看的,權當參考。
前序遍歷
遞歸:很簡單,按照根左右的邏輯訪問就是。
public List<Integer> preorderTraversal(TreeNode root){
List<Integer> res = new ArrayList<Integer>();
inorder(root, res);
return res;
}
public void preorder(TreeNode root, List<Integer> res){
if(root == null) return;
res.add(root.val);
preorder(root.left, res);
preorder(root.right, res);
}
在所有樹狀結構的數據結構當中,遞歸真的是一種最最省事的寫法了。如果題目沒有要求,建議直接遞歸就vans了。
非遞歸(這纔是本篇文章的重點):用一個棧,前序遍歷的非遞歸是三種非遞歸當中最好理解的一種了,先把根節點入棧,再遵循當前節點彈棧->當前節點左節點入棧->當前節點右節點入棧即可。
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
Stack<TreeNode> stack = new Stack<TreeNode>();
if(root != null){
stack.push(root);
}
TreeNode temp;
while(!stack.isEmpty()){
temp = stack.pop();
res.add(temp.val);
if(temp.right != null){
stack.push(temp.right);
}
if(temp.left != null){
stack.push(temp.left);
}
}
return res;
}
中序遍歷
遞歸:還是很簡單,左根右結構的訪問即可。
public List<Integer> inorderTraversal(TreeNode root){
List<Integer> res = new ArrayList<Integer>();
inorder(root, res);
return res;
}
public void inorder(TreeNode root, List<Integer> res){
if(root == null) return;
inorder(root.left, res);
res.add(root.val);
inorder(root.right, res);
}
非遞歸:使用一個棧,因爲是左根右結構的遍歷,所以看到當前節點有左節點就先把它加入棧,直到沒有左節點了,再彈棧當前節點,如果當前節點有右子樹的話就切換到右子樹上即可。
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
Stack<TreeNode> stack = new Stack<TreeNode>();
TreeNode temp = root; //temp變量只在入棧預處理階段使用
TreeNode cur;
while(temp != null || !stack.isEmpty()){
// 只要有左節點,就壓棧
if(temp != null){
stack.push(temp);
temp = temp.left;
}else{
// 沒有左節點了,訪問當前節點
cur = stack.pop();
res.add(cur.val);
// 有右子樹的話就切換到右子樹上
if(cur.right != null){
temp = cur.right;
}
}
}
return res;
}
後序遍歷
遞歸:……(省略)
public List<Integer> inorderTraversal(TreeNode root){
List<Integer> res = new ArrayList<Integer>();
inorder(root, res);
return res;
}
public void inorder(TreeNode root, List<Integer> res){
if(root == null) return;
inorder(root.left, res);
inorder(root.right, res);
res.add(root.val);
}
非遞歸:也是使用一個棧,這裏和中序遍歷的非遞歸不同之處在於,要用一個指針last記錄下上一個節點,如果是從last過來的,就直接遍歷當前節點結束(因爲此時已經走完了一輪左->右->根的流程),如果不是就切換到右子樹上去。
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
Stack<TreeNode> stack = new Stack<TreeNode>();
TreeNode temp = root; //temp變量只在入棧預處理階段使用
TreeNode cur;
TreeNode last = null; // last和cur只在出棧遍歷階段使用
while(temp != null || !stack.isEmpty()){
// 只要有左節點,就壓棧
if(temp != null){
stack.push(temp);
temp = temp.left;
}else{
// 確定是否切換到右子樹
// 如果有右節點,又不是剛剛從右子樹過來的,就切換到右子樹
cur = stack.peek();
if(cur.right != null && cur.right != last){
temp = cur.right;
}else{
// 左右都遍歷完了,遍歷當前節點並彈棧
// 記錄last節點
res.add(cur.val);
stack.pop();
last = cur;
}
}
}
return res;
}
層序遍歷
這個一般人一開始沒法想到遞歸了。但是也很簡單,用一個隊列保存節點即可。這個順序很好理解,遍歷到一個節點的時候,就把它的左節點->右節點加入隊列,只要隊列不空,就一直訪問下去。
還有一個問題是如何記錄層的信息,即如何把每一層的節點分開的問題。只要在每一層開始的時候,用queue.size()記錄下隊列中當前的節點數,這就是當前這一層的所有節點了。然後用一個變量保存遍歷過的節點數,只要這個變量與size()相等了,說明當前這一層遍歷完了,清空變量再用size()得到下一層的節點數,如此往復即可。
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> levels = new ArrayList<List<Integer>>();
if (root == null) return levels;
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(root);
int level = 0;
while ( !queue.isEmpty() ) {
levels.add(new ArrayList<Integer>());
int level_length = queue.size(); //記錄當前層的長度
// 在當前層裏記錄每一個節點,同時把下一層的節點加入隊列
for(int i = 0; i < level_length; ++i) {
TreeNode node = queue.poll();
levels.get(level).add(node.val);
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
level++;
}
return levels;
}
但是層序遍歷其實也可以通過遞歸來求解的,下面貼出遞歸的代碼:
List<List<Integer>> levels = new ArrayList<List<Integer>>();
public void helper(TreeNode node, int level) {
// start the current level
if (levels.size() == level)
levels.add(new ArrayList<Integer>());
// fulfil the current level
levels.get(level).add(node.val);
// process child nodes for the next level
if (node.left != null)
helper(node.left, level + 1);
if (node.right != null)
helper(node.right, level + 1);
}
public List<List<Integer>> levelOrder(TreeNode root) {
if (root == null) return levels;
helper(root, 0);
return levels;
}
以上層序遍歷的兩種解法均來自leetcode官方題解,遞歸解法用得少,只需要掌握非遞歸解法即可。
其實上面說到的非遞歸解法中,前中後序遍歷的三種解法可以視作是dfs,而最後一種層序遍歷則可視作是bfs。這也啓發我們想到,dfs相關的思想(甚至是回溯相關的思想)可以用棧來解決,而bfs可以用隊列來解決。在不用遞歸的情況下,做出遞歸的效果,一般依靠的都是這兩種數據結構。