写在前面
这种题回过头来再做一下,别有一番风味。之前没看过题解中的 Morris 遍历方法,只是简单的AC然后下一题,可能也不是很明所以然,借此记录一下 Morris 遍历(好像是KMP中的M)。
递归遍历一遍与迭代
根据前序遍历的特点,很容易写出前序遍历的思路,就是打印当前值,然后遍历其左右子树,其中的栈是由系统自动维护的。
- 遍历的过程中同时写入相应的元素值,因此该递归函数是不需要返回值的,那就说明它需要传入参数(不设置全局变量的话)
void traverse(TreeNode* root,vector<int> & res) { if(root==nullptr) return; res.push_back(root->val); traverse(root->left,res); traverse(root->right,res); }
与迭代的区别: 需要自己显式的维护一个栈,其实也就是在遍历到当前节点时,在写入该元素值时,将结点入栈,等需要它的时候再出栈(左孩子为空),进行右子树的处理。
分解成子问题的递归
前序遍历的思路也可以这样理解,就是将该节点、该节点左孩子的前序遍历、该节点右孩子的前序遍历拼接起来,是不是就可以通过分解成子任务来完成。
- 对于递归函数,一定要写清楚三要素:终止条件、传入参数、返回值
很明显:对于分解子问题是需要有返回值,因为对于根节点,它需要整合来自左右子树的前序遍历。
// 返回值类型及传入参数类型 vector<int> traverse(TreeNode* root) { // 终止条件 if(root==nullptr) return {}; vector<int> left=traverse(root->left); vector<int> right=traverse(root->right); vector<int> res={root->val}; res.insert(res.end(),left.begin(),left.end()); res.insert(res.end(),right.begin(),right.end()); return res; }
Morris 遍历
看了一些介绍,Morris遍历就是在将树进行线索化,其实之前的递归利用栈的特性也是一种线索化,具体来说就是栈里面保存了下一步需要访问的点。因为前序遍历直观上来看是从上一直往左下角遍历,然后遇到空指针回溯遍历右侧;栈中其实就是保存了线索化的信息。Morris遍历就是利用其中的空指针来减少内存空间使用,利用这些将这棵树线索化。
如何线索化与去线索化
结合这个视频和官方解答,可以快速的了解它是如何线索化以及去线索化的,视频来源。
线索化具体来说:
其实就是要明白什么时候需要用到栈中节点的信息(栈不存在,利用线索化信息找到)
- 对于当前节点p:其实就是当该节点的左孩子(如果存在)的右子树遍历完了就会用到当前节点 p 的信息。这就对应 官解 中所描述的:在当前节点的左子树中找到当前节点在中序遍历下的前驱节点 Q;因为该节点(Q节点)遍历完后就会用到当前节点(p节点)的右子树信息。
去线索化具体来说:
其实对于有左子树的点,它会被遍历两次,一次是入栈前,另外一次是“栈内弹出”的;前者是发生在遍历左子树之前,后者发生在遍历左子树之后。如果发生在之前,则需要将其线索化,反之则需要将其去线索化,将二叉树还原。
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
if(root==nullptr)
return {};
vector<int> ans;
TreeNode* p1=root;
TreeNode* p2=nullptr;
while(p1!=nullptr)
{
p2=p1->left;
// 先进行线索化
if(p2!=nullptr)
{
// 找到当前节点p1的中序遍历的前驱节点,其实就是p1左子树的最右节点
while(p2->right!=nullptr && p2->right!=p1)
{
p2=p2->right;
}
// 如果因为p2->right为空,则说明还没有线索化
if(p2->right==nullptr)
// 没有线索化则构建线索化
{
p2->right=p1;
// 写入当前值
ans.push_back(p1->val);
// 索引左子树
p1=p1->left;
// 避免索引右子树
continue;
}
else
// 如果有,说明左侧已经访问过,则断开该线索
p2->right=nullptr;
}
else
{
// 左子树为空则直接写入数值
ans.push_back(p1->val);
}
// 遍历右子树
p1=p1->right;
}
return ans;
}
};
$\cdots$ end $\cdots$