天天看點

算法總結 - 樹 - 周遊 - 基本周遊類型Traversal(周遊)

Tree

  • Traversal(周遊)
    • 基本周遊類型
      • 1. Inorder(中序)
        • (1) 遞歸
        • (2) 使用棧疊代
        • (3) Morris周遊(虛拟節點)
      • 2. Preorder( 前序)
        • (1)遞歸
        • (2)使用棧疊代
        • (3)Morris周遊
      • 3. Postorder( 後序)
        • (1) 遞歸
        • (2) 使用棧疊代
        • (3) Morris周遊
      • 4. Iterator( 疊代器)
      • 5. 類似題目

Traversal(周遊)

基本周遊類型

1. Inorder(中序)

94. Binary Tree Inorder Traversal

Given a binary tree, return the inorder traversal of its nodes’ values.

Example:

Input: [1,null,2,3]
   1
    \
     2
    /
   3
           

Output: [1,3,2]

(1) 遞歸

TC: O(n) SC: O(n)

public static void inOrder(Node root) {
   if (root == null) return;
   inOrder(root.left);
   System.out.print(root.val + " ");
   inOrder(root.right);
}
           

(2) 使用棧疊代

解題思路

使用棧以及一個周遊指針

TC: O(n) SC: O(n)

public List<Integer> inorderTraversal(TreeNode root) {
    List<Integer> res = new ArrayList<>();
    if (root == null) return res;
    
    Deque<TreeNode> stk = new ArrayDeque<>();
    TreeNode p = root;
    while(p != null || !stk.isEmpty()){
        if (p != null){
            stk.push(p);
            p = p.left;
        }else if (!stk.isEmpty()){
            TreeNode cur = stk.pop();
            res.add(cur.val);
            p = cur.right;
        }
    }
    return res;
}
           

(3) Morris周遊(虛拟節點)

解題思路

一般面試考周遊考遞歸或者疊代的算法比較常見,morris周遊比較難,如果想提高印象分可以使用,我會在另一篇部落格中詳細說明這個算法

TC: O(n) SC: O(1)

public List<Integer> inorderTraversal(TreeNode root) {
    List<Integer> res = new ArrayList<>();
    if (root == null) return res;
    
    TreeNode p1 = root;
    TreeNode p2 = null;
    
    while(p1 != null){
        p2 = p1.left;
        if (p2 != null){
            while(p2.right != null && p2.right != p1){
                p2 = p2.right;
            }
            if (p2.right == p1){
                p2.right = null;
            }else{
                p2.right = p1;
                p1 = p1.left;
                continue;
            }
        }
        res.add(p1.val);
        p1 = p1.right;
    }
    return res;
}
           

2. Preorder( 前序)

Tree: Preorder Traversal

Complete the preOrder function in your editor below, which has parameter: a pointer to the root of a binary tree. It must print the values in the tree’s preorder traversal as a single line of space-separated values.

Input Format

Our hidden tester code passes the root node of a binary tree to your preOrder function.

Constraints

Nodes in the tree

Output Format

Print the tree’s preorder traversal as a single line of space-separated values.

Sample Input

1
      \
       2
        \
         5
        /  \
       3    6
        \
         4  
           

Sample Output

1 2 5 3 4 6 
           

(1)遞歸

TC:O(n) SC: O(n)

public static void preOrder(Node root) {
     if (root == null) return;
     System.out.print(root.data + " ");
     preOrder(root.left);
     preOrder(root.right);
 }
           

(2)使用棧疊代

TC:O(n) SC: O(n)

解題思路

每個節點出棧即列印,子節點從右往左壓棧

public static void preOrder(Node root) {
    Deque<Node> stk = new ArrayDeque<>();
    stk.push(root);
    while(!stk.isEmpty()){
        Node cur = stk.pop();
        System.out.print(cur.data + " " );
        if (cur.right != null) stk.push(cur.right);
        if (cur.left != null) stk.push(cur.left);
    }
}
           

(3)Morris周遊

TC:O(n) SC: O(1)

解題思路

和中序周遊相比,如果改節點不存在左孩子則直接列印目前節點

public static void preOrder(Node root) {
    if (root == null)return;
    Node p1 = root;
    Node p2 = null;

    while(p1 != null){
        p2 = p1.left;
        if(p2 != null){
            while(p2.right != null && p2.right != p1){
                p2 = p2.right;
            }
            if (p2.right == p1){
                p2.right = null;
            }else{
                System.out.print(p1.data + " ");
                p2.right = p1;
                p1 = p1.left;
                continue;
            }
        }else{
            System.out.print(p1.data + " ");
        }
        p1 = p1.right;
    }
}
           

3. Postorder( 後序)

Tree: Postorder Traversal

Complete the postOrder function in your editor below, which has parameter: a pointer to the root of a binary tree. It must print the values in the tree’s postorder traversal as a single line of space-separated values.

Input Format

Our hidden tester code passes the root node of a binary tree to your postOrder function.

Constraints

1 Nodes in the tree 500

Output Format

Print the tree’s postorder traversal as a single line of space-separated values.

Sample Input

1
   \
    2
     \
      5
     /  \
    3    6
     \
      4
           

Sample Output

4 3 6 5 2 1

(1) 遞歸

TC:O(n) SC: O(n)

public static void postOrder(Node root) {
   if (root == null) return;        
    postOrder(root.left);
    postOrder(root.right);
    System.out.print(root.val + " ");
}
           

(2) 使用棧疊代

第一種比較hacky的方式就是利用list的addFirst這個性質。許多轉換周遊順序的題目可以用這個方法修改輸出。但是實際周遊并不是符合題目要求的,有比較較真的面試官會要求實作完全符合周遊要求的算法。

public List<Integer> postorderTraversal(TreeNode root) {
	LinkedList<Integer> ans = new LinkedList<>();
	Stack<TreeNode> stack = new Stack<>();
	if (root == null) return ans;
	
	stack.push(root);
	while (!stack.isEmpty()) {
		TreeNode cur = stack.pop();
		ans.addFirst(cur.val);
		if (cur.left != null) {
			stack.push(cur.left);
		}
		if (cur.right != null) {
			stack.push(cur.right);
		} 
	}
	return ans;
}
           

這種算法就是完全符合後序周遊,但是也有需要注意的一點,就是如果将指針節點設定為null,那麼當出現子節點的父節點沒有右邊節點時會fail第一個if statement,而這個子節點永遠不會入棧。是以指針節點p必須確定初始的時候不與樹中任何的節點相等。

public List<Integer> postorderTraversal(TreeNode root) {
    List<Integer> res = new ArrayList<>();
    if (root == null) return res;
    Deque<TreeNode> stk = new ArrayDeque<>();

    stk.push(root);
    TreeNode p = new TreeNode(-9999999);
    
    while(!stk.isEmpty()){
        TreeNode peak = stk.peek();
        if (peak.left != null && p != peak.left && p != peak.right){
            stk.push(peak.left);
        }else if (peak.right != null && p != peak.right){
            stk.push(peak.right);
        }else{
            res.add(stk.pop().val);
            p = peak;
        }
    }
    return res;
}
           

(3) Morris周遊

解題思路

依次逆序列印右子樹的邊

TC:O(n) SC: O(1)

public static void postOrder(Node root) {
    if (root == null)return;
    Node p1 = root;
    Node p2 = null;

    while(p1 != null){
        p2 = p1.left;
        if(p2 != null){
            while(p2.right != null && p2.right != p1){
                p2 = p2.right;
            }
            if (p2.right == p1){
                p2.right = null;
                printEdge(p1.left);
            }else{
                p2.right = p1;
                p1 = p1.left;
                continue;
            }
        }
        p1 = p1.right;
    }
    printEdge(root);
}

private static void printEdge(Node root){
    Node tail = reverse(root);
    Node cur = tail;
    while(cur != null){
        System.out.print(cur.data + " ");
        cur = cur.right;
    }
    reverse(tail);
}

private static Node reverse(Node root){
    Node pre = null;
    Node cur = root;
    while(cur != null){
        Node next = cur.right;
        cur.right = pre;
        pre = cur;
        cur = next;
    }
    return pre;
}
           

4. Iterator( 疊代器)

173. Binary Search Tree Iterator

Implement an iterator over a binary search tree (BST). Your iterator will be initialized with the root node of a BST.

Calling next() will return the next smallest number in the BST.

Example:

算法總結 - 樹 - 周遊 - 基本周遊類型Traversal(周遊)
BSTIterator iterator = new BSTIterator(root);
iterator.next();    // return 3
iterator.next();    // return 7
iterator.hasNext(); // return true
iterator.next();    // return 9
iterator.hasNext(); // return true
iterator.next();    // return 15
iterator.hasNext(); // return true
iterator.next();    // return 20
iterator.hasNext(); // return false
           

Note:

next() and hasNext() should run in average O(1) time and uses O(h) memory, where h is the height of the tree.

You may assume that next() call will always be valid, that is, there will be at least a next smallest number in the BST when next() is called.

其實就是把中序周遊包了一層疊代器的皮,需要把初始壓棧和周遊節點分成兩個部分,符合疊代器的規範就行。

class BSTIterator {
    Deque<TreeNode> stk;
    TreeNode p;
    public BSTIterator(TreeNode root) {
        stk = new ArrayDeque<TreeNode>();
        p = root;
        while(p != null){
            stk.push(p);
            p = p.left;
        }
    }
    
    /** @return the next smallest number */
    public int next() {
        int res = 0;
        if (hasNext()){
            TreeNode cur = stk.pop();
            res = cur.val;
            cur = cur.right;
            while(cur != null){
                stk.push(cur);
                cur = cur.left;
            }
        }
        return res;
    }
    
    /** @return whether we have a next smallest number */
    public boolean hasNext() {
        return !stk.isEmpty();
    }
}
           

5. 類似題目

589. N-ary Tree Preorder Traversal

590. N-ary Tree Postorder Traversal

這個就是非常直覺的binary tree traversal的擴充,不同之處僅僅在于左右子節點變成了一個子節點的list。

繼續閱讀