系列文章目录
前言
每天进步一点点
一、背景
给定一个二叉树的根节点 root ,返回它的 中序 遍历。
二、我的思路
1、递归
从根节点,一直往左走,在这个过程中遇到的值用栈保留下来,知道走到最左边,然后开始输出(存到另一个数组里面)。再访问栈里的元素,再访问这个元素有无右孩子,循环如此。最后是直到没有右孩子了,就结束。
递归肯定是最简单的,但是这里要求返回一个数组,就是以前从来是直接打印的,这一次需要存储起来。
我就想着吧,声明一个全局的数组,然后每次计算的时候,用这个数组去接收,但是还是出问题了。(完事代码不见了)
之后我看了别人的代码,发现是用了两个函数,把数组作为函数的参数进行传递(为啥不能直接接收?下次我复原一下我的代码想法)
好了,通过的代码是:
/**
* 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:
void inorderTraversal2(TreeNode* root,vector<int>& in_order)
{
if(root==NULL) return;
inorderTraversal2(root->left,in_order);
in_order.push_back(root->val);
inorderTraversal2(root->right,in_order);
}
vector<int> inorderTraversal(TreeNode* root) {
vector<int> in_order;
inorderTraversal2(root,in_order);
return in_order;
}
};
2、迭代
从根节点,一直往左走,在这个过程中遇到的值用栈保留下来,直到走到最左边,然后开始输出(存到另一个数组里面)。再访问栈里的元素,出栈,后访问这个元素有无右孩子,循环如此。最后是栈空了,就结束。
代码如下:
/**
* 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> inorderTraversal(TreeNode* root) {
vector<int> in_order;
stack<TreeNode*> store;
if(root==NULL)
{
return {};
}
else store.push(root);
while(!store.empty())
{
TreeNode* p=store.top()->left;
store.top()->left=NULL;//根节点的左指针也置空
while(p!=NULL)
{
store.push(p);
//把进栈的结点左指针置空
TreeNode* r=p;
p=p->left;
r->left=NULL;
}
//左节点到头了,然后出栈进入数组
TreeNode* q=store.top();
in_order.push_back(q->val);
store.pop();
//访问右孩子,不空进栈
if(q->right!=NULL)
store.push(q->right);
}
return in_order;
}
};
三、官方的思路
递归的就不讲了,这里主要是将迭代和线索(Morris 中序遍历)。
1、迭代
大致思路大差不差,就是在第二个循环前面那里有些困扰。就是当访问过左子树时,再次回到根节点,由于我第二个循环进入是判断栈顶元素,所以又访问了一次左边元素,这明显是重复访问的。
于是我就把访问过左节点的元素左指针全部置为空,这样就不会进入第二个循环了。但是官方的思路就很巧妙,它进入第二个循环的判断是root是否为空,而不是取栈顶元素(看他左右两边访问过没有)。
看代码:
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> stk;
while (root != nullptr || !stk.empty()) {
while (root != nullptr) {
stk.push(root);
root = root->left;
}
root = stk.top();
stk.pop();
res.push_back(root->val);
root = root->right;
}
return res;
}
};
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/binary-tree-inorder-traversal/solution/er-cha-shu-de-zhong-xu-bian-li-by-leetcode-solutio/
来源:力扣(LeetCode)
官方判断root,意味着上一次给root赋值为左子树最右边的那个空结点时,这个时候怎么回到根节点呢?因为第二个循环是判断root空不空,而此时root本身为空,所以当然进不了第二个循环,然后再次取栈顶元素(此时即为根节点),顺势访问了右子树。妙啊!!!
2、Morris 中序遍历
感觉就是线索二叉树。迭代的办法是用栈去保留中间结点,这里它直接改变孩子的右指针的指向,使之指向中序遍历的下一个元素,这样就不用栈来保存了,依次访问即可。
代码如下:
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
TreeNode *predecessor = nullptr;
while (root != nullptr) {
if (root->left != nullptr) {
// predecessor 节点就是当前 root 节点向左走一步,然后一直向右走至无法走为止
predecessor = root->left;
while (predecessor->right != nullptr && predecessor->right != root) {
predecessor = predecessor->right;
}
// 让 predecessor 的右指针指向 root,继续遍历左子树
if (predecessor->right == nullptr) {
predecessor->right = root;
root = root->left;
}
// 说明左子树已经访问完了,我们需要断开链接
else {
res.push_back(root->val);
predecessor->right = nullptr;
root = root->right;
}
}
// 如果没有左孩子,则直接访问右孩子
else {
res.push_back(root->val);
root = root->right;
}
}
return res;
}
};
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/binary-tree-inorder-traversal/solution/er-cha-shu-de-zhong-xu-bian-li-by-leetcode-solutio/
来源:力扣(LeetCode)
总结
思路是比较清晰地,就是实现起来,要考虑到很多的细节,就容易卡住。我总喜欢把第一个元素单独拿出来放进去,但是好的代码总是一起判断的,本质区别在于,我不知道如何设置循环的终止条件,就是不知道要判断什么信息。这给我一个思路就是,写代码不要着急写,知道用循环或是递归的时候,先把出口搞明白,是比较重要的。
喜欢就点个赞叭!!!