题目:二叉树的中序遍历
描述
二叉树的中序遍历总所周知遍历顺序为:左子树,根节点,右子树,同时对左右子树仍然采用中序遍历(左子树,根节点,右子树)。
方法1:采用递归,代码比较简单,直接看代码。时间空间复杂度均为O(n),n为树的节点数量。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> ans;
vector<int> inorderTraversal(TreeNode* root) {
if (root == nullptr) return ans;
dfs(root);
return ans;
}
void dfs(TreeNode* root) {
if (root == nullptr) return;
if (root->left == nullptr && root->right == nullptr) {//到达叶子节点
ans.push_back(root->val);
return;
}
dfs(root->left);//访问左子树
ans.push_back(root->val);//访问根节点
dfs(root->right);//访问右子树
}
};
方法2:使用栈,递归本质上就是使用了栈,此时我们手工模拟栈遍历即可。时间空间复杂度和方法1一样,都是O(n)
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
stack<TreeNode*> s;
vector<int> ans;
while (root || !s.empty()) {
while (root) {
s.push(root);
root = root->left;
}//以上保证左子树现在为空(null),因为已经到了最左边的一个节点
root = s.top();
s.pop();//左子树为null,所以可以访问根节点,将根节点弹出,访问
ans.push_back(root->val);
root = root->right;//左子树和根节点都已经访问过,访问右子树
}
return ans;
}
};
方法3:Morris遍历,以上两种方法空间复杂度均为O(n),该方法空间复杂度只需要O(1)
leetcode官方题解对该算法过程描述非常精确,搬运过来(写博客也是方便复习,自己写很难写的比这个出色,不再献丑)
自己对该算法的理解:Morris遍历利用了这样一个特性,当第一次访问到节点x时,首先查看x节点左子树中最右边的节点,假设该节点为pre,因为pre是左子树最右边的节点,所以pre的右孩子此时一定为空,Morris算法的关键就算利用了pre->right,第一次访问x时将为空的pre->right指向x,x=x->left,一直这样向下,直到到达叶子节点,访问叶子节点,然后访问右子树x=x->right(注意叶子节点x就是pre节点)。
当遍历完x的左子树再一次访问到x时,此时pre->right不为空,通过pre->right不为空可以知道此时已经遍历完左子树,直接遍历根节点x即可,然后遍历右子树x=x->right,同时将pre->right复原置为null。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
while (root) {
if (root->left) {//当前节点有左子树
TreeNode *pre = root->left;//找到当前节点的先驱
while (pre->right && pre->right != root) pre = pre->right;
if (pre->right == nullptr) {//先驱节点右子树为空,
pre->right = root;//先驱节点右孩子修改为根节点,做标记
root = root->left;//向左遍历
}
else {//先驱右孩子不空,这是上次遍历时的标记,说明此时已经访问完左子树
res.push_back(root->val);//访问根节点
pre->right = nullptr;
root = root->right;//访问右子树
}
}
else {//当前节点没有左子树,此时到达最左边,可以认为左子树访问完毕
res.push_back(root->val);//访问根节点
root = root->right;//访问右子树
}
}
return res;
}
};