Binary Tree Preorder Traversal

Question

  1. Given a binary tree, return the preorder traversal of its nodes' values.
  2. Note
  3. Given binary tree {1,#,2,3},
  4. 1
  5. \
  6. 2
  7. /
  8. 3
  9. return [1,2,3].
  10. Example
  11. Challenge
  12. Can you do it without recursion?

題解1 - 遞迴

面試時不推薦遞迴這種做法。

遞迴版很好理解,首先判斷當前節點(根節點)是否為null,是則返回空vector,否則先返回當前節點的值,然後對當前節點的左節點遞迴,最後對當前節點的右節點遞迴。遞迴時對返回結果的處理方式不同可進一步細分為遍歷和分治兩種方法。

譯註:也不是完全不能這麼做,不過以二元樹的遍歷來說,遞迴方法太容易實現,面試官很可能進一步要求迭代的方法,並且有可能會問遞迴的缺點(連續呼叫函數導致stack的overflow問題),不過如果遍歷並不是題幹而只是解決方法的步驟,用簡單的迭代方式實現有時亦無不可且可以減少錯誤,因此務必要和面試官充分溝通,另即使迭代寫不出來只寫出遞迴版本也要好過完全寫不出東西。

Python - Divide and Conquer

  1. """
  2. Definition of TreeNode:
  3. class TreeNode:
  4. def __init__(self, val):
  5. this.val = val
  6. this.left, this.right = None, None
  7. """
  8. class Solution:
  9. """
  10. @param root: The root of binary tree.
  11. @return: Preorder in ArrayList which contains node values.
  12. """
  13. def preorderTraversal(self, root):
  14. if root == None:
  15. return []
  16. return [root.val] + self.preorderTraversal(root.left) \
  17. + self.preorderTraversal(root.right)

C++ - Divide and Conquer

  1. /**
  2. * Definition of TreeNode:
  3. * class TreeNode {
  4. * public:
  5. * int val;
  6. * TreeNode *left, *right;
  7. * TreeNode(int val) {
  8. * this->val = val;
  9. * this->left = this->right = NULL;
  10. * }
  11. * }
  12. */
  13. class Solution {
  14. public:
  15. /**
  16. * @param root: The root of binary tree.
  17. * @return: Preorder in vector which contains node values.
  18. */
  19. vector<int> preorderTraversal(TreeNode *root) {
  20. vector<int> result;
  21. if (root != NULL) {
  22. // Divide (分)
  23. vector<int> left = preorderTraversal(root->left);
  24. vector<int> right = preorderTraversal(root->right);
  25. // Merge
  26. result.push_back(root->val);
  27. result.insert(result.end(), left.begin(), left.end());
  28. result.insert(result.end(), right.begin(), right.end());
  29. }
  30. return result;
  31. }
  32. };

C++ - Traversal

  1. /**
  2. * Definition of TreeNode:
  3. * class TreeNode {
  4. * public:
  5. * int val;
  6. * TreeNode *left, *right;
  7. * TreeNode(int val) {
  8. * this->val = val;
  9. * this->left = this->right = NULL;
  10. * }
  11. * }
  12. */
  13. class Solution {
  14. public:
  15. /**
  16. * @param root: The root of binary tree.
  17. * @return: Preorder in vector which contains node values.
  18. */
  19. vector<int> preorderTraversal(TreeNode *root) {
  20. vector<int> result;
  21. traverse(root, result);
  22. return result;
  23. }
  24. private:
  25. void traverse(TreeNode *root, vector<int> &ret) {
  26. if (root != NULL) {
  27. ret.push_back(root->val);
  28. traverse(root->left, ret);
  29. traverse(root->right, ret);
  30. }
  31. }
  32. };

Java - Divide and Conquer

  1. /**
  2. * Definition for a binary tree node.
  3. * public class TreeNode {
  4. * int val;
  5. * TreeNode left;
  6. * TreeNode right;
  7. * TreeNode(int x) { val = x; }
  8. * }
  9. */
  10. public class Solution {
  11. public List<Integer> preorderTraversal(TreeNode root) {
  12. List<Integer> result = new ArrayList<Integer>();
  13. if (root != null) {
  14. // Divide
  15. List<Integer> left = preorderTraversal(root.left);
  16. List<Integer> right = preorderTraversal(root.right);
  17. // Merge
  18. result.add(root.val);
  19. result.addAll(left);
  20. result.addAll(right);
  21. }
  22. return result;
  23. }
  24. }

源碼分析

使用遍歷的方法保存遞迴返回結果需要使用輔助遞迴函數traverse,將結果作為參數傳入遞迴函數中,傳值時注意應使用vector的引用。
分治方法首先分開計算各結果,最後合並到最終結果中。
C++ 中由於是使用vector, 將新的vector插入另一vector不能再使用push_back, 而應該使用insert。
Java 中使用addAll方法.

複雜度分析

遍歷樹中節點,時間複雜度 O(n), 未使用額外空間(不包括呼叫函數的stack開銷)。

題解2 - 迭代

迭代時需要利用堆疊來保存遍歷到的節點,紙上畫圖分析後發現應首先進行出堆疊拋出當前節點,保存當前節點的值,隨後將右、左節點分別進入堆疊(注意進入堆疊順序,先右後左),迭代到其為葉子節點(NULL)為止。

Python

  1. # Definition for a binary tree node.
  2. # class TreeNode:
  3. # def __init__(self, x):
  4. # self.val = x
  5. # self.left = None
  6. # self.right = None
  7. class Solution:
  8. # @param {TreeNode} root
  9. # @return {integer[]}
  10. def preorderTraversal(self, root):
  11. if root is None:
  12. return []
  13. result = []
  14. s = []
  15. s.append(root)
  16. while s:
  17. root = s.pop()
  18. result.append(root.val)
  19. if root.right is not None:
  20. s.append(root.right)
  21. if root.left is not None:
  22. s.append(root.left)
  23. return result

C++

  1. /**
  2. * Definition of TreeNode:
  3. * class TreeNode {
  4. * public:
  5. * int val;
  6. * TreeNode *left, *right;
  7. * TreeNode(int val) {
  8. * this->val = val;
  9. * this->left = this->right = NULL;
  10. * }
  11. * }
  12. */
  13. class Solution {
  14. public:
  15. /**
  16. * @param root: The root of binary tree.
  17. * @return: Preorder in vector which contains node values.
  18. */
  19. vector<int> preorderTraversal(TreeNode *root) {
  20. vector<int> result;
  21. if (root == NULL) return result;
  22. stack<TreeNode *> s;
  23. s.push(root);
  24. while (!s.empty()) {
  25. TreeNode *node = s.top();
  26. s.pop();
  27. result.push_back(node->val);
  28. if (node->right != NULL) {
  29. s.push(node->right);
  30. }
  31. if (node->left != NULL) {
  32. s.push(node->left);
  33. }
  34. }
  35. return result;
  36. }
  37. };

Java

  1. /**
  2. * Definition for a binary tree node.
  3. * public class TreeNode {
  4. * int val;
  5. * TreeNode left;
  6. * TreeNode right;
  7. * TreeNode(int x) { val = x; }
  8. * }
  9. */
  10. public class Solution {
  11. public List<Integer> preorderTraversal(TreeNode root) {
  12. List<Integer> result = new ArrayList<Integer>();
  13. if (root == null) return result;
  14. Stack<TreeNode> s = new Stack<TreeNode>();
  15. s.push(root);
  16. while (!s.empty()) {
  17. TreeNode node = s.pop();
  18. result.add(node.val);
  19. if (node.right != null) s.push(node.right);
  20. if (node.left != null) s.push(node.left);
  21. }
  22. return result;
  23. }
  24. }

源碼分析

  1. 對root進行異常處理
  2. 將root壓入堆疊
  3. 循環終止條件為堆疊s為空,所有元素均已處理完
  4. 訪問當前堆疊頂元素(首先取出堆疊頂元素,隨後pop掉堆疊頂元素)並存入最終結果
  5. 將右、左節點分別壓入堆疊內,以便取元素時為先左後右。
  6. 返回最終結果

其中步驟4,5,6為迭代的核心,對應前序遍歷「根左右」。

所以說到底,使用迭代,只不過是另外一種形式的遞迴。使用遞迴的思想去理解遍歷問題會容易理解許多。

複雜度分析

使用輔助堆疊,最壞情況下堆疊空間與節點數相等,空間複雜度近似為 O(n), 對每個節點遍歷一次,時間複雜度近似為 O(n).