前言
`
本文将主要围绕以下问题进行讨论:
二叉树的中序遍历。
本文将分别以三种方法【递归】【迭代】【莫里斯算法】来解决这一问题(文章末尾附完整代码)
一、如何理解中序遍历?
二叉树的中序遍历是一种常见的遍历方式,主要按照 “左子树 - 根节点 - 右子树” 的顺序访问二叉树的每个节点。
如下:
4
/ \
2 6
/ \ / \
1 3 5 7
首先访问左子树,对于节点 4,先访问其左子树中的节点 2。对于节点 2,再访问其左子树中的节点 1。节点 1 没有左子树,将其值 1 加入列表。然后访问节点 2,将其值 2 加入列表。接着访问节点 2 的右子树中的节点 3,将其值 3 加入列表。
再访问根节点,此时访问节点 4,将其值 4 加入列表。
最后访问右子树,对于节点 4 的右子树中的节点 6。先访问节点 6 的左子树中的节点 5,将其值 5 加入列表。然后访问节点 6,将其值 6 加入列表。接着访问节点 6 的右子树中的节点 7,将其值 7 加入列表。
题目链接: [点击跳转] Leetcode 94. 二叉树的中序遍历
二、方法一(递归法)
递归法通过深度优先搜索,将二叉树的所有节点全部按照中序的方式遍历出来。
根据示例所给出的输出格式,我们需要定义一个vector类型的变量,并且在最后输出这个变量。同时,我们还应注意到,如果将空节点传入递归函数这种,应该返回一个空值。
vector<int> tre;
void preOrder(TreeNode *root) {
if (root == nullptr) {
return;
}
}
这个时候,我们就可以根据前序的“左-根-右”原则进行排序和递归啦!
这个递归函数不需要返回值,因为我们最后输出内容为tre这个向量,而函数只需要去递归遍历所有节点。
vector<int> tre;
void inorder(TreeNode* root) {
if (root==nullptr) {
return;
}
inorder(root->left);
tre.push_back(root->val);
inorder(root->right);
}
注意:这里的 if 判断中 return 后空着即可。
完整代码如下:
/**
* 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> tre;
void inorder(TreeNode* root) {
if (root==nullptr) {
return;
}
inorder(root->left);
tre.push_back(root->val);
inorder(root->right);
}
vector<int> inorderTraversal(TreeNode* root) {
inorder(root);
return tre;
}
};
时间复杂度为O(n)。
三、方法二(迭代法)
将遍历结果存储在一个整数向量tre中,同时定义一个存储二叉树节点指针的栈。
vector<int> tre;
stack<TreeNode*> sta;
首先需要判断输入节点是否为空。如若节点不为空且栈不为空(栈不为空,说明二叉树的节点没有全部处理完),则进入内层循环将当前节点压入栈中,再指向左子节点。
vector<int> tre;
stack<TreeNode*> sta;
vector<int> inorderTraversal(TreeNode* root) {
while(root!=nullptr||!sta.empty()){
while(root!=nullptr){
sta.push(root);
root=root->left;
}
return tre;
}
经过如上处理后,就需要考虑将栈中元素按照中序传入向量tre中(注意:在取栈顶元素后,一定要记得将其出栈)
vector<int> tre;
stack<TreeNode*> sta;
vector<int> inorderTraversal(TreeNode* root) {
while(root!=nullptr||!sta.empty()){
while(root!=nullptr){
sta.push(root);
root=root->left;
}
root=sta.top();
sta.pop();
tre.push_back(root->val);
root=root->right;
}
return tre;
}
完整代码如下:
/**
* 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> tre;
stack<TreeNode*> sta;
vector<int> inorderTraversal(TreeNode* root) {
while(root!=nullptr||!sta.empty()){
while(root!=nullptr){
sta.push(root);
root=root->left;
}
root=sta.top();
sta.pop();
tre.push_back(root->val);
root=root->right;
}
return tre;
}
};
时间复杂度为O(n)。
四、方法三(莫里斯算法)
如果既不想用递归,也不愿用辅助栈该如何实现呢?
莫里斯算法利用了二叉树中叶子节点的右指针为空这一特性。由于不需要递归和栈,空间复杂度为O(1)。
其核心思想是在遍历过程中,对于每个节点,如果其左子树不为空,就找到其左子树中的最右节点(即中序遍历下该节点的前驱节点),然后将前驱节点的右指针指向当前节点。
这样在遍历完左子树后,可以通过这个指针回到当前节点。如果前驱节点的右指针已经指向当前节点,说明左子树已经遍历完,此时将前驱节点的右指针置为空,然后访问当前节点,并继续遍历当前节点的右子树。
总之,我们就需要记住:
找到 根节点的 左子树 的 最右节点,然后就这个最右节点指向根节点。
如下:
4
/ \
2 6
/ \ / \
1 3 5 7
找到根节点4下的左子树,如下:
2
/ \
1 3
然后找到其最右节点3。
将3指向根节点4。
然后将根节点设置为2,重复上述步骤。
在把4,2,1为根节点的操作重复后,回溯到节点2,然后找节点2的右子节点3,继续重复步骤。
然后再回溯到节点4,找节点4的右子节点6,然后继续。
完整代码如下:
/**
* 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> tre;
TreeNode* pre;
vector<int> inorderTraversal(TreeNode* root) {
while (root!= nullptr) {
if (root->left == nullptr) {
tre.push_back(root->val);
root = root->right;
} else {
pre = root->left;
while (pre->right!= nullptr && pre->right!= root) {
pre = pre->right;
}
if (pre->right == nullptr) {
pre->right = root;
root = root->left;
} else {
pre->right = nullptr;
tre.push_back(root->val);
root = root->right;
}
}
}
return tre;
}
};
时间复杂度为O(n)。
空间复杂度为O(1)。