class Solution {
public:
void midOrder(TreeNode* root,vector&ans){
if(!root) return;
midOrder(root->left,ans);
ans.push_back(root->val);
midOrder(root->right,ans);
}
vector inorderTraversal(TreeNode* root) {
vectorans;
midOrder(root,ans);
return ans;
}
};
中序和前序的写法很相似,只是变换了一下推入数组的语句的位置。
class Solution {
public:
vector inorderTraversal(TreeNode* root) {
vectorans;
stackS;
// 栈不为空或者指针不为空时说明没遍历完
while(!S.empty() || root!=nullptr){
while(root != nullptr){
// 把左子树推入栈内
S.push(root);
root = root->left;
}
// 左子树遍历完,回退到栈顶,弹出栈顶
root = S.top();
ans.push_back(root->val);
S.pop();
// 遍历右子树
root = root->right;
}
return ans;
}
};
要注意的是,C++中的stack的pop()方法返回值是void,所以如果想提取栈顶元素,只能用top方法。
这种方法精妙在它不利用递归,也不用栈来维护。它的空间复杂度就为O(1)。核心思想是利用树里大量的空指针。整个过程就相当于:设当前遍历到的节点为x,找到其左子树的最右边的节点(也就是相当于x中序遍历的前驱节点),并令其的右孩子指向x,则我们无需用栈去维护整个路径了,在左子树遍历完毕以后就可以通过这个自设的指针走回到x,接下来就继续访问右子树即可。一句话概括就是:将所有右孩子为空的节点的右儿子指向其后继节点。
对于当前根节点x,中序遍历整体算法就是:
1.x若无左孩子,直接输出x的值到数组,再访问其右孩子,x=x->right
2.x若有左孩子,找到x在中序遍历的前驱节点(也就是x左子树最右的节点),设为pre。
1)若pre的右孩子为空,将右孩子指向x,x=x->left
2)若pre的右孩子不为空,说明已进行过操作1),则此时pre的右孩子为x,说明x的左子树遍历完毕,x=x->right,输出x的值,并令pre的右孩子置空【相当于访问完节点后会还原树
class Solution {
public:
vector inorderTraversal(TreeNode* root) {
vectorans;
TreeNode *pre = nullptr;
// 循环结束条件是指针为空
while(root != nullptr){
// 当根节点左孩子为空时,直接输出根节点,并遍历右子树
if(root->left == nullptr){
ans.emplace_back(root->val);
root = root->right;
}else{
// 根节点左孩子不为空时
pre = root->left;
// 找根节点的前驱节点,就是左子树的最右边节点
while(pre->right != nullptr && pre->right != root){
pre = pre->right;
}
// pre为空时,使其右孩子指向根节点,同时根节点继续向左遍历
if(pre->right == nullptr){
pre->right = root;
root = root->left;
}else {
//pre不为空,说明左子树已经遍历过了,输出当前节点的值,并遍历右子树
ans.emplace_back(root->val);
root = root->right;
pre->right = nullptr;
}
}
}
return ans;
}
};
class Solution {
public:
void frontOrder(TreeNode* root,vector&ans){
if(!root) return;
ans.push_back(root->val);
frontOrder(root->left,ans);
frontOrder(root->right,ans);
}
vector preorderTraversal(TreeNode* root) {
vectorans;
frontOrder(root,ans);
return ans;
}
};
递归隐式地维护了一个栈,而迭代只不过是显式地将这个栈模拟出来。
class Solution {
public:
vector preorderTraversal(TreeNode* root) {
vectorans;
stackS;
// 栈不为空或者指针不为空时说明没遍历完
while(!S.empty() || root != nullptr){
while(root != nullptr){
// 把左子树推入栈内,区别在于一读到根节点就输出结果
ans.push_back(root->val);
S.push(root);
root = root->left;
}
// 空指针退栈
root = S.top();
S.pop();
// 遍历右子树
root = root->right;
}
return ans;
}
};
程序跟中序遍历的Morris写法是基本一致的,有差异的地方仅仅在于emplace_back的一条语句位置发生了变化。变化在于:我们知道当root有左孩子时,我们才开始找root的中序遍历的前驱节点prev,并有以下判断情况:1.当prev的右孩子为空时,说明左子树未开始遍历 2.prev的右孩子不为空时,说明左子树已经遍历完了,此时的root是回溯之后的根节点【因为遍历到左子树倒数第二个最左边的节点y时,prev是其左儿子z,并使其指回了y,遍历到左子树最后一个节点z时,此时z没有左孩子了,他会直接开始访问右孩子,也就是之前prev指回的y】。
所以想要完成前序遍历,只需要在第一次访问该节点的时候就输出即可,也就是上述的判断情况1.而中序遍历则需要在遍历完左子树并回溯到根节点的时候才能输出,也就是上述判断情况2.
class Solution {
public:
vector preorderTraversal(TreeNode* root) {
vectorans;
TreeNode *prev = nullptr;
while(root!=nullptr){
if(root->left != nullptr){
prev = root->left;
while(prev->right!=nullptr && prev->right!=root){
prev = prev->right;
}
if(prev->right == nullptr){
//说明此时还未遍历左子树,直接输出根节点,并开始遍历左子树
ans.emplace_back(root->val);
prev->right = root;
root = root->left;
}else{
//此时左子树已经遍历过了,回到了根节点,直接置空prev,不用再输出一遍根节点了
// 直接访问右子树
prev->right = nullptr;
root = root->right;
}
}else{
// 没有左子树的时候直接输出根节点并访问右子树
ans.emplace_back(root->val);
root = root->right;
}
}
return ans;
}
};
class Solution {
public:
void lastOrder(TreeNode* root,vector&ans){
if(!root) return;
lastOrder(root->left,ans);
lastOrder(root->right,ans);
ans.push_back(root->val);
}
vector postorderTraversal(TreeNode* root) {
vectorans;
lastOrder(root,ans);
return ans;
}
};
是在非递归前序遍历的基础上通过一点修改,变成根右左。然后再利用STL中的reverse翻转数组得到左右根,从而实现迭代后序遍历。
class Solution {
public:
vector postorderTraversal(TreeNode* root) {
vectorans;
stackS;
// 栈不为空或者指针不为空时说明没遍历完
while(!S.empty() || root != nullptr){
while(root != nullptr){
// 把右子树推入栈内,一读到根节点就输出结果 变成根右左
ans.push_back(root->val);
S.push(root);
root = root->right;
}
// 空指针退栈
root = S.top();
S.pop();
// 遍历左子树
root = root->left;
}
reverse(ans.begin(),ans.end());
return ans;
}
};
传统的栈遍历方法。跟中序非递归遍历的区别在于,中序遍历时是确定左子树遍历完了,然后就可以访问根结点,接着遍历右子树。但是后序遍历在遍历完左子树时,要确定右子树是否遍历过了或者是否为空,当右子树为空或者遍历完了才能访问根节点。因此我们需要定义一个TreeNode型指针来记录历史访问记录,判断回溯到父节点时,上一个访问的节点是否为右子树。
/**
* 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 postorderTraversal(TreeNode* root) {
vectorans;
stackS;
TreeNode *prev = nullptr;
// 栈不为空或者指针不为空时说明没遍历完
while(!S.empty() || root != nullptr){
while(root != nullptr){
S.push(root);
root = root->left;
}
// 左子树遍历完,回溯到根节点
root = S.top();
// 如果右子树为空,或者已经遍历过右子树,则直接访问根节点
if(root->right == nullptr || prev == root->right){
S.pop();
ans.push_back(root->val);
// 更新历史记录
prev = root;
//根节点已被访问,记得置空根指针
root = nullptr;
}else{
// 否则继续遍历右子树
root = root->right;
}
}
return ans;
}
};
class Solution {
public:
void addPath(vector &vec, TreeNode *node) {
int count = 0;
while (node != nullptr) {
++count;
vec.emplace_back(node->val);
node = node->right;
}
reverse(vec.end() - count, vec.end());
}
vector postorderTraversal(TreeNode *root) {
vector res;
if (root == nullptr) {
return res;
}
TreeNode *p1 = root, *p2 = nullptr;
while (p1 != nullptr) {
p2 = p1->left;
if (p2 != nullptr) {
while (p2->right != nullptr && p2->right != p1) {
p2 = p2->right;
}
if (p2->right == nullptr) {
p2->right = p1;
p1 = p1->left;
continue;
} else {
p2->right = nullptr;
addPath(res, p1->left);
}
}
p1 = p1->right;
}
addPath(res, root);
return res;
}
};