题目
给定一个二叉树的根节点 root ,返回它的 中序 遍历。
提示:
树中节点数目在范围 [0, 100] 内
-100 <= Node.val <= 100
进阶: 递归算法很简单,你可以通过迭代算法完成吗?
LC94. 二叉树的中序遍历
LC144. 二叉树的前序遍历
LC145. 二叉树的后序遍历
注:主要掌握迭代方式遍历
思路
中序遍历:按照左节点->根节点->右节点的顺序进行遍历。二叉树中每个节点都采用这种方式。
递归
采用递归函数进行编写,属于基本操作,这里不进行讲解。
设N为二叉树的节点数:
时间复杂度:O(n)
空间复杂度:O(n)
迭代——左节点存栈(最主要方式)
递归本质上是系统通过栈来实现的,因此采用遍历时我们可以进行模拟一种栈结构。
- 建立一个stack数据结构存储二叉树节点指针,建立一个容器存储遍历内容。
- 从根节点开始访问,以下内容while迭代进行。
- 根据中序遍历顺序,首先要访问左节点。因此对于访问到的每一个节点root,首先通过while循环到root左子树的最左侧的节点的左侧,并依次将中间经历的节点压入到栈内,用于后续访问(可以发现,如果一个节点有左子节点,则在栈内它的左子节点一定在它上面)。
- 此时将一个节点出栈,这个节点可能是它的左节点为空的节点,也可能是它的左节点已经出栈并记录过了。不管是哪种情况,它都不需要考虑左子节点的问题,因此只需将改节点存储的值记录在容器内。
- 将节点指针指向3中出栈节点的右节点(左节点、根节点访问过,此时只需要访问右节点)
时间复杂度:O(n)
空间复杂度:O(n)
迭代——右节点存栈
此方法较复杂,相对上一方法有许多冗余之处,不建议采用。
- 建立一个stack数据结构存储二叉树节点指针,建立一个容器存储遍历内容。
- 从根节点开始访问,以下内容while迭代进行。
- 如果当前访问的节点左子节点为空,则记录当前节点。并将节点数值标记为数值以外的数,是为了说明该节点已经记录完毕,这里记为-101。最后将指针指向该节点的右子节点。
- 如果当前访问节点的左节点的数值为标记数值,说明已经遍历过了,则将该节点记录。最后将指针指向该节点的右子节点。
- 如果没有以上情况,首先压栈当前节点的右节点,再压栈当前节点,然后访问左节点。
时间复杂度:O(n)
空间复杂度:O(n)
该方法的缺点是有许多冗余步骤,同时二叉树内部数值被改变,在并发程序中可能会造成一些问题。
morris遍历
该方法也是一个迭代方式,核心是找到当前节点在中序遍历所处位置前一个节点——左子树的最右侧一个节点。与前面迭代方式有所不同的是,该方法不需要采用栈来记录节点,而是利用二叉树中空闲的指针,节省了空间的开销。
思路: 对于每一个当前访问节点,想要对该节点的数值进行记录,那么说明它的在中序遍历中的前驱节点predecessor已经记录完毕了。有两种方式可以表明它的前驱节点记录完毕:一种是对前驱节点原地数值标记,但是会对二叉树进行破坏;第二种是将这个前驱节点predecessor的右子树指向当前访问节点,因为这是当前节点在中序遍历上的前一个节点,说明这个前驱节点原本是没有右子树的,所以其右子树指针属于空闲状态。当某次迭代时发现这个前驱节点右子树就是当前节点时,说明这个前驱节点记录完毕。
- 建立一个容器存储遍历内容。
- 从根节点开始访问,以下内容while迭代进行。
- 如果当前访问节点的左子树不为空,找到该左子树的最右侧的一个节点predecessor,判断依据如下:若为空,将predecessor的右子节点指向当前访问节点。然后将访问指针指向当前访问节点的子节点。若predecessor右子节点指向目前访问的节点,说明当前访问节点的左子树已经全部遍历完成,则把当前节点数值记录,然后将访问指针指向它的右子节点(记得再把这个predecessor的链接断开,一方面是不破坏二叉树结构,另一方面是有些二叉树结构会形成一个闭环导致死循环出现)。
- 如果当前访问节点的左子树为空,记录当前节点的数值,然后将访问指针指向它的右子节点。
这里判断predecessor右子节点指向目前访问的节点可能有点难理解,我们借助图以根节点root的判断为例:
- root首先访问它的左子树,从中找到左子树中最右侧的节点,图中为2。此时将2这个节点的右子节点指向根节点
- 然后开始遍历root的左子节点,也就是4。4也按照上述办法进行。
- 。。。。。。
- 当1、7、4三个节点都遍历完成之后,指针指向2这个节点。
- 2因为没有左子节点,2这个数值被记录进容器,然后开始指针指向2的右子节点,即为根节点。但是我们没有标记根节点,所以并不知道他是根节点root。
- 于是根节点又重复了一遍刚才的操作,root首先访问它的左子树,从中找到左子树中最右侧的节点,此时它发现中间有个节点(这里是2)指向的是自己,他就知道这个操作已经做了一遍,根节点root的左子树全部遍历,便可记录自己的数值。
时间复杂度:O(n)
空间复杂度:O(n),不需要栈,但是需要一个容器存遍历结果,所以还是O(n)
代码
递归
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<int> ans;
vector<int> inorderTraversal(TreeNode* root) {
get_data(root);
return ans;
}
void get_data(TreeNode* root){
if(root==nullptr)
return;
get_data(root->left);
ans.push_back(root->val);
get_data(root->right);
}
};
迭代——左节点存储
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> ans;
while(1){
while(root!=nullptr){
st.push(root);
root = root->left;
}
if(st.empty()) break;
root = st.top();
st.pop();
ans.push_back(root->val);
root = root->right;
}
return ans;
}
};
迭代——右节点存储
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> ans;
if(root == nullptr) return ans;
stack<TreeNode*> st;
if(root->right!=nullptr)
st.push(root->right);
st.push(root);
root = root->left;
while(1){
if(root == nullptr){
if(st.empty()) break;
root = st.top();
st.pop();
}
if(root->left==nullptr||(root->left!=nullptr&&root->left->val==-101)){
ans.push_back(root->val);
root->val = -101;
if(root->right!=nullptr&&(st.empty()||!st.empty()&&root->right!=st.top()))
root = root->right;
else{
if(st.empty()) break;
root = st.top();
st.pop();
}
}
else{
if(root->right!=nullptr)
st.push(root->right);
st.push(root);
root = root->left;
}
}
return ans;
}
};
morris遍历
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> ans;
while(root!=nullptr){
if(root->left!=nullptr){
TreeNode* predecessor = root->left;
while(predecessor->right!=nullptr&&predecessor->right!=root)
predecessor = predecessor->right;
if(predecessor->right==nullptr){
predecessor->right = root;
root = root->left;
}
else if(predecessor->right==root){
ans.push_back(root->val);
predecessor->right = nullptr; //如果不及时断掉,有可能会陷到一个环里出不来
root = root->right;
}
}
else{
ans.push_back(root->val);
root = root->right;
}
}
return ans;
}
};