基於Java的二叉樹的三種遍歷方式的遞歸與非遞歸實現

二叉樹的遍歷方式包括前序遍歷中序遍歷後序遍歷,其實現方式包括遞歸實現非遞歸實現java

前序遍歷:根節點 | 左子樹 | 右子樹node

中序遍歷:左子樹 | 根節點 | 右子樹post

後序遍歷:左子樹 | 右子樹 | 根節點指針

1. 遞歸實現

遞歸方式實現代碼十分簡潔,三種遍歷方式的遞歸實現代碼結構相同,只是執行順序有所區別。code

前序遍歷:遞歸

public class preOrderRecur {
    List<Integer> res = new ArrayList<>();
    public List<Integer> preOrderTraversal(TreeNode root) {
        if (root != null) {
            res.add(root.val); // 根節點
            preOrderTraversal(root.left); // 左子樹
            preOrderTraversal(root.right); // 右子樹
        }
        return res;
    }
}

中序遍歷:class

public class inOrderRecur {
	List<Integer> res = new ArrayList<>();
	public List<Integer> inOrderTraversal(TreeNode root) {
        if (root != null) {
            inOrderTraversal(root.left); // 左子樹
            res.add(root.val); // 根節點
            inOrderTraversal(root.right); // 右子樹
        }
    }
    return res;
}

後序遍歷:List

public class inOrderRecur {
	List<Integer> res = new ArrayList<>();
	public List<Integer> inOrderTraversal(TreeNode root) {
        if (root != null) {
            inOrderTraversal(root.left); // 左子樹
            inOrderTraversal(root.right); // 右子樹
            res.add(root.val); // 根節點
        }
    }
    return res;
}

2. 迭代實現

2.1 使用輔助棧——空間複雜度O(N)

2.1.1 中序遍歷
  • 從當前結點一直向其最左孩子搜索,直到沒有左孩子了中止,這個過程當中將路程中的全部結點入棧;
  • 彈出棧頂元素,將其記錄在答案中,並把當前結點置爲彈出元素的右孩子並重復第一步過程。
public class inOrderIterator {
    List<Integer> res = new ArrayList<>();
    public List<Integer> inOrderTraversal(TreeNode root) {
        Stack<TreeNode> stack = new Stack<>();
        while (root != null || !stack.isEmpty()) {
            if (root != null) {
                stack.push(root);
                root = root.left;
            } else {
                TreeNode node = stack.pop();
                res.add(node.val);
                root = node.right;
            }
        }
        return res;
    }
}
2.1.2 前序遍歷

方法1:由於前序遍歷訪問順序是「中-左-右」,因此能夠先將根結點壓棧,而後按照下列步驟執行。二叉樹

  • 若是棧不爲空,則彈出棧頂元素存入結果中;
  • 若是彈出元素的右孩子不爲空則將右孩子壓棧,而後若是其左孩子也不爲空將其左孩子壓棧(由於棧是後入先出的,因此爲了達到「中-左-右」的順序,須要先壓入右孩子,再壓入左孩子)。
public class preOrderIterator {
    List<Integer> res = new ArrayList<>();
    public List<Integer> inOrderTraversal(TreeNode root) {
        if (root == null) return res;
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while (!stack.isEmpty()) {
            root = stack.pop();
            res.add(root.val);
            // 右孩子壓棧
            if (root.right != null) stack.push(root.right);
            // 左孩子壓棧
            if (root.left != null) stack.push(root.left);
        }
        return res;
    }
}

方法2:根據中序遍歷進行微調:搜索

public class preOrderIterator {
    List<Integer> res = new ArrayList<>();
    public List<Integer> inOrderTraversal(TreeNode root) {
        Stack<TreeNode> stack = new Stack<>();
        while (root != null || !stack.isEmpty()) {
            if (root != null) {
                res.add(root.val);
                stack.push(root);
                root = root.left;
            } else {
                TreeNode node = stack.pop();
                root = node.right;
            }
        }
        return res;
    }
}
2.1.3 後序遍歷

由於前序遍歷的順序是「左-中-右」,然後序遍歷順序是「左-右-中」,不考慮左結點,區別只是在於中結點和右結點的順序進行了反向而已,所以可使用前序遍歷的代碼進行調整,只須要將前序遍歷對左右孩子壓棧的順序反向便可,即先壓入左孩子,再壓入右孩子。除此以外,由於按照這種方法調整獲得的遍歷順序爲「中-右-左」,正好是後序遍歷的反向順序,所以在得到遍歷序列後還需進行逆序操做。

public class postOrderIterator {
    List<Integer> res = new LinkedList<>();
    public List<Integer> postOrderTraversal(TreeNode root) {
        if (root == null) return res;
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while (!stack.isEmpty()) {
            root = stack.pop();
            // 頭插法
            res.add(0, root.val);
            // 左孩子壓棧
            if (root.left != null) stack.push(root.left);
            // 右孩子壓棧
            if (root.right != null) stack.push(root.right);
        }
        return res;
    }
}

2.2 Morris遍歷——空間複雜度O(1)

該方法的思路簡單說就是,對於每個結點,找到它左孩子的最右子結點,由於按照正常訪問順序,其左孩子的最有子節點訪問完後就應該訪問其自己了,所以將其左孩子最右子節點的右指針指向它。基本步驟以下:

  • 若是當前結點左孩子爲空,說明最左邊訪問完畢,將其置爲其右孩子
  • 若是當前結點左孩子不爲空,那麼開始嘗試找到該結點左孩子的最右子節點,創建鏈接關係
    • 若是找到的當前結點的左孩子的最右子節點右指針爲空,說明還未創建鏈接關係,是首次訪問當前結點,那麼將該最右結點的右指針指向當前結點,而後當前結點向左孩子走一步繼續重複全部步驟。
    • 若是找到的當前結點的左孩子的最右子節點右指針不爲空,說明已創建過鏈接關係,是第二次訪問當前結點,這意味着當前結點的左子樹應該已經所有遍歷完了,此時應恢復鏈接關係從新置爲空,而後當前結點向右孩子走一步繼續重複全部步驟。

該方法雖然保證了O(1)的空間複雜度,但在遍歷過程當中改變了部分結點的指向,破壞了樹的結構。

2.2.1 中序遍歷
public class inOrderMorris {
    List<Integer> res = new ArrayList<>();
    public List<Integer> inOrderTraversal(TreeNode root) {
        TreeNode pre = null;
        TreeNode cur = root;
        while (cur != null) {
            if (cur.left == null) {
                res.add(cur.val);
                cur = cur.right;
            } else {
                pre = cur.left;
                while (pre.right != null && pre.right != cur) pre = pre.right;
                if (pre.right == null) {
                    pre.right = cur;
                    cur = cur.left;
                } else {
                    res.add(cur.val);
                    pre.right = null;
                    cur = cur.right;
                }
            }
        }
        return res;
    }
}
2.2.2 前序遍歷
public class preOrderMorris {
    List<Integer> res = new ArrayList<>();
    public List<Integer> preOrderTraversal(TreeNode root) {
        TreeNode pre = null;
        TreeNode cur = root;
        while (cur != null) {
            if (cur.left == null) {
                res.add(cur.val);
                cur = cur.right;
            } else {
                pre = cur.left;
                while (pre.right != null && pre.right != cur) pre = pre.right;
                if (pre.right == null) {
                    res.add(cur.val);
                    pre.right = cur;
                    cur = cur.left;
                } else {
                    pre.right = null;
                    cur = cur.right;
                }
            }
        }
        return res;
    }
}
2.2.3 後序遍歷

前序遍歷反向的思想

public class postOrderMorris {
    List<Integer> res = new LinkedList<>();
    public List<Integer> postOrderTraversal(TreeNode root) {
        TreeNode pre = null;
        TreeNode cur = root;
        while (cur != null) {
            if (cur.right == null) {
                res.add(0, cur.val);
                cur = cur.left;
            } else {
                pre = cur.right;
                while (pre.left != null && pre.left != cur) pre = pre.left;
                if (pre.left == null) {
                    res.add(0, cur.val);
                    pre.left = cur;
                    cur = cur.right;
                } else {
                    pre.left = null;
                    cur = cur.left;
                }
            }
        }
        return res;
    }
}
相關文章
相關標籤/搜索