一、遞迴方法
遞迴比較簡單,直接上程式碼:
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 方法日後有時間再研究。