Source
Given a binary tree, return the preorder traversal of its nodes' values. Note Given binary tree {1,#,2,3}, 1 \ 2 / 3 return [1,2,3]. Example Challenge Can you do it without recursion?
题解1 - 递归
递归版很好理解,首先判断当前节点(根节点)是否为 null,是则返回空 vector,否则先返回当前节点的值,然后对当前节点的左节点递归,最后对当前节点的右节点递归。递归时对返回结果的处理方式不同可进一步细分为遍历和分治两种方法。
C++ - Divide and Conquer
/** * Definition of TreeNode: * class TreeNode { * public: * int val; * TreeNode *left, *right; * TreeNode(int val) { * this->val = val; * this->left = this->right = NULL; * } * } */ class Solution { public: /** * @param root: The root of binary tree. * @return: Preorder in vector which contains node values. */ vector<int> preorderTraversal(TreeNode *root) { vector<int> result; if (root != NULL) { // Divide vector<int> left = preorderTraversal(root->left); vector<int> right = preorderTraversal(root->right); // Merge result.push_back(root->val); result.insert(result.end(), left.begin(), left.end()); result.insert(result.end(), right.begin(), right.end()); } return result; } };
C++ - Traversal
/** * Definition of TreeNode: * class TreeNode { * public: * int val; * TreeNode *left, *right; * TreeNode(int val) { * this->val = val; * this->left = this->right = NULL; * } * } */ class Solution { public: /** * @param root: The root of binary tree. * @return: Preorder in vector which contains node values. */ vector<int> preorderTraversal(TreeNode *root) { vector<int> result; traverse(root, result); return result; } private: void traverse(TreeNode *root, vector<int> &ret) { if (root != NULL) { ret.push_back(root->val); traverse(root->left, ret); traverse(root->right, ret); } } };
Java - Divide and Conquer
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ public class Solution { public List<Integer> preorderTraversal(TreeNode root) { List<Integer> result = new ArrayList<Integer>(); if (root != null) { // Divide List<Integer> left = preorderTraversal(root.left); List<Integer> right = preorderTraversal(root.right); // Merge result.add(root.val); result.addAll(left); result.addAll(right); } return result; } }
Java - Traversal
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ public class Solution { private List<Integer> result = new ArrayList<Integer>(); public List<Integer> preorderTraversal(TreeNode root) { if (root != null) { result.add(root.val); preorderTraversal(root.left); preorderTraversal(root.right); } return result; } }
源码分析
使用遍历的方法保存递归返回结果需要使用辅助递归函数traverse,将结果作为参数传入递归函数中,传值时注意应使用vector的引用。另外一个方法则是使用类的私有变量保存最终结果。 分治方法首先分开计算各结果,最后合并到最终结果中。 C++ 中由于是使用vector, 将新的vector插入另一vector不能再使用push_back, 而应该使用insert。 Java 中使用addAll方法.
复杂度分析
遍历树中节点,时间复杂度 O(n), 遍历的方法未使用额外空间,分治的方法生成了临时变量,最差情况下空间复杂度为 (n).
题解2 - 迭代
迭代时需要利用栈来保存遍历到的节点,纸上画图分析后发现应首先进行出栈抛出当前节点,保存当前节点的值,随后将右、左节点分别入栈(注意入栈顺序,先右后左),迭代到其为叶子节点(NULL)为止。
C++
/** * Definition of TreeNode: * class TreeNode { * public: * int val; * TreeNode *left, *right; * TreeNode(int val) { * this->val = val; * this->left = this->right = NULL; * } * } */ class Solution { public: /** * @param root: The root of binary tree. * @return: Preorder in vector which contains node values. */ vector<int> preorderTraversal(TreeNode *root) { vector<int> result; if (root == NULL) return result; stack<TreeNode *> s; s.push(root); while (!s.empty()) { TreeNode *node = s.top(); s.pop(); result.push_back(node->val); if (node->right != NULL) { s.push(node->right); } if (node->left != NULL) { s.push(node->left); } } return result; } };
Java
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ public class Solution { public List<Integer> preorderTraversal(TreeNode root) { List<Integer> result = new ArrayList<Integer>(); Deque<TreeNode> stack = new ArrayDeque<TreeNode>(); if (root != null) stack.push(root); while (!stack.isEmpty()) { root = stack.pop(); result.add(root.val); if (root.right != null) stack.push(root.right); if (root.left != null) stack.push(root.left); } return result; } }
源码分析
- 对root进行异常处理
- 将root压入栈
- 循环终止条件为栈s为空,所有元素均已处理完
- 访问当前栈顶元素(首先取出栈顶元素,随后pop掉栈顶元素)并存入最终结果
- 将右、左节点分别压入栈内,以便取元素时为先左后右。
- 返回最终结果
其中步骤4,5,6为迭代的核心,对应前序遍历「根左右」。
所以说到底,使用迭代,只不过是另外一种形式的递归。使用递归的思想去理解遍历问题会容易理解许多。