二叉樹的四種遍歷方式(遞歸和非遞歸)

這篇文章的目的只是整理幾個常用的二叉樹遞歸/非遞歸遍歷的模板函數,方便用到的時候憑藉記憶寫出來即可。內容沒多少技術含量,寫給自己看的,權當參考。

前序遍歷

遞歸:很簡單,按照根左右的邏輯訪問就是。

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可以用隊列來解決。在不用遞歸的情況下,做出遞歸的效果,一般依靠的都是這兩種數據結構。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章