遞迴和迭代實現二叉樹先序、中序、後序和層序遍歷

Ethan_Wong發表於2021-08-04

一、遞迴方法

遞迴比較簡單,直接上程式碼:

1.1 先序遍歷

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    List<Integer> res = new ArrayList<Integer>();
    public List<Integer> preorderTraversal(TreeNode root) { 
        if(root == null){
            return res;
        }
        //將樹節點的值儲存在 List 中 便於後續輸出
        res.add(root.val);
        preorderTraversal(root.left);
        preorderTraversal(root.right);
        return res;
    }
}

1.2 中序遍歷

class Solution {
    List<Integer> res = new ArrayList<Integer>();
    public List<Integer> inorderTraversal(TreeNode root) { 
        if(root == null){
            return res;
        }
        inorderTraversal(root.left);
        res.add(root.val);
        inorderTraversal(root.right);
        return res;
}

1.3 後序遍歷

class Solution {
    List<Integer> res = new ArrayList<Integer>();
    public List<Integer> postorderTraversal(TreeNode root) { 
        if(root == null){
            return res;
        }
        postorderTraversal(root.left);
        postorderTraversal(root.right);
        res.add(root.val);
        return res;
}    

二、迭代方法

能夠用遞迴方法解決的問題基本都能用非遞迴方法實現。因為遞迴方法無非是利用函式棧來儲存資訊,可以尋找相應的資料結構替代函式棧,同樣可以實現相同的功能。下面用棧,類比遞迴方法來統一實現三種遍歷方式:

2.1 先序遍歷

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
    	Stack<TreeNode> nodeStack = new Stack<TreeNode>();
    	TreeNode node = root;
        while(node != null || !nodeStack.isEmpty()) { //當指標節點為空,遍歷完所有節點時跳出迴圈
            if(node != null) { //依此遍歷當前樹最左邊的節點。根據遞迴方法,挨個加入輸出 list 中
               res.add(node.val);
               nodeStack.push(node);
               node = node.left;
            }else { //遍歷完再看右子樹
               node = nodeStack.pop();
               node = node.right;
            }
        }
        return res;
    }
}

2.2 中序遍歷

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
    	Stack<TreeNode> nodeStack = new Stack<TreeNode>();
    	TreeNode node = root;
        while(node != null || !nodeStack.isEmpty()) { //當指標節點為空,遍歷完所有節點時跳出迴圈
            if(node != null) { //依此遍歷當前樹最左邊的節點
               nodeStack.push(node);
               node = node.left;
            }else { //遍歷完左子樹最左節點後,根據遞迴方法,挨個加入進輸出 list 中再看右子樹
               node = nodeStack.pop();
               res.add(node.val);
               node = node.right;
            }
        }
        return res;
    }
}

2.3 後序遍歷

其實後序遍歷,可以利用前序遍歷中先遍歷右子樹,形成 根->右子樹->左子樹 和後序完全相反的順序,然後再將該順序逆序,最後得到後序遍歷的順序。

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        Stack<TreeNode> nodeStack = new Stack<TreeNode>();
        Stack<TreeNode> rStack = new Stack<TreeNode>(); //用一個棧來進行最後 List 反轉
        TreeNode node = root;
        while(node != null || !nodeStack.isEmpty()) { //當指標節點為空,遍歷完所有節點時跳出迴圈
            if(node != null) { //依此遍歷當前樹最右邊的節點
               rStack.push(node);
               nodeStack.push(node);
               node = node.right;
            }else { //遍歷完右子樹最右節點
               node = nodeStack.pop();
               node = node.left;
            }
        }
        while(!rStack.isEmpty()){
            res.add(rStack.pop().val);
        }
        return res;
    }
}

2.4 層序遍歷

利用佇列來實現層序遍歷

基本思想是:

  • 入隊就出隊,並判斷是否有子節點,使用當前佇列中的元素作為限制條件
    • 有則入隊,沒有下一步
  • 當所有子節點為空,且全部節點出隊後迴圈結束,輸出佇列
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        //設定返回陣列和佇列
        List<List<Integer>> res = new ArrayList<List<Integer>>();
        Queue<TreeNode> Q = new LinkedList<TreeNode>();
        if(root == null) {
            return res;
        }
        Q.offer(root);
        //判斷條件
        while(!Q.isEmpty()) {
            int size = Q.size();
            List<Integer> list = new ArrayList<Integer>();
            for(int i = 1; i <= size; i++) {
                TreeNode pnode = Q.poll();
                list.add(pnode.val);
                if(pnode.left != null) {
                    Q.offer(pnode.left);
                }
                if(pnode.right != null){
                    Q.offer(pnode.right);
                }
            }
            res.add(list);
        }
        return res;
    }
}

三、Morris 方法

最後無論是遞迴還是迭代方法,最後程式跑完結果需要的記憶體開銷還是很大。這是由二叉樹的結構所決定的,每個節點都有指向孩子節點的指標,但是沒有指向父節點的指標,所以需要利用棧來實現子節點回到父節點的效果。

Morris 遍歷的實質就是避免利用棧結構,讓下層節點擁有指向上層的指標,具體是通過讓底層節點指向 null 的空閒指標指向上層的某個節點,到達子節點指向父節點的效果。

詳情可參考該部落格, morris 方法日後有時間再研究。

Morris 演算法進行二叉樹遍歷

相關文章