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


一、遞歸方法

遞歸比較簡單,直接上代碼:

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 算法進行二叉樹遍歷


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM