算法随想Day12【二叉树】| LC144、LC145、LC94-二叉树的前中后序遍历
LC144、LC145、LC94-二叉树的前中后遍历
二叉树递归遍历
比较容易实现
struct TreeNode
{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x = 0) : val(x), left(nullptr), right(nullptr) { }
};
class Tree
{
public:
vector<int> preorderTraversal(TreeNode* root)
{
vector<int> vct;
pre_traversal(root, vct);
return vct;
}
void pre_traversal(TreeNode* curr, vector<int>& vct)
{
if(curr == nullptr)
{
return;
}
// 前序遍历--中左右
vct.emplace_back(curr->val);
pre_traversal(curr->left, vct);
pre_traversal(curr->right, vct);
}
vector<int> inorderTraversal(TreeNode* root)
{
vector<int> vct;
in_traversal(root, vct);
return vct;
}
void in_traversal(TreeNode* curr, vector<int>& vct)
{
if(curr == nullptr)
{
return;
}
// 中序遍历--左中右
in_traversal(curr->left, vct);
vct.emplace_back(curr->val);
in_traversal(curr->right, vct);
}
vector<int> postorderTraversal(TreeNode* root)
{
vector<int> vct;
post_traversal(root, vct);
return vct;
}
void post_traversal(TreeNode* curr, vector<int>& vct)
{
if(curr == nullptr)
{
return;
}
// 后序遍历--左右中
post_traversal(curr->left, vct);
vct.emplace_back(curr->val);
post_traversal(curr->right, vct);
}
};
二叉树非递归遍历
迭代法需要通过使用“栈”来模拟递归的过程
前序遍历:
放入root节点,弹出每个节点时,依次push入该节点的右孩子和左孩子
后序遍历:
由中左右->中右左,翻转结果中右左->左右中,即为后续遍历
中序遍历:
无法像上述后序由前序调换“左右”位置即得到结果一样,因为对"中"节点的访问顺序和处理顺序不一致。
class Tree
{
public:
// 前序遍历--中左右
vector<int> preorderTraversal_iteration(TreeNode* root)
{
vector<int> vct;
stack<TreeNode*> stk;
if(root == nullptr)
return vct;
stk.push(root);
while(!stk.empty())
{
TreeNode* curr = stk.top();
vct.emplace_back(curr->val);
stk.pop();
if(curr->right) stk.push(curr->right);
if(curr->left) stk.push(curr->left);
}
return vct;
}
// 后序遍历--左右中,即前序中的中左右,现改为中右左,在将vct整体镜像翻转所得
vector<int> postorderTraversal_iteration(TreeNode* root)
{
vector<int> vct;
stack<TreeNode*> stk;
if(root == nullptr)
{
return vct;
}
stk.push(root);
while(!stk.empty())
{
TreeNode* curr = stk.top();
vct.emplace_back(curr->val);
stk.pop();
if(curr->left) stk.push(curr->left);
if(curr->right) stk.push(curr->right);
}
reverse(vct.begin(), vct.end());
return vct;
}
};
二叉树非递归遍历(统一写法)
每个节点都会经历三个阶段:
- 被访问,push入栈
- 被处理,节点value放入vector
- pop出栈
前序遍历(中左右 -- 访问和处理同步):
- 处理的时机:访问的同时,即可处理。如最开始访问root时,就可以sta.push()
- pop的时机:对某个节点,处理完左子树,并回溯时,用来找到右子树后,即可pop
中序遍历(左中右 -- 访问和处理不同步):
- 处理的时机:如root被访问并push入栈后,没有立即处理,而是一路向左直到访问其left孩子不存在时,才会回溯处理
- pop的时机:回溯处理后,下一步是访问其右子树,用来找到right孩子后,即可pop
后序遍历(左右中 -- 访问和处理不同步):
-
处理的时机:对某个节点,被访问后push入栈。没有立即处理,一路向左直到访问其left孩子不存在时,撤回访问其right孩子,这两步都结束了才再次撤回处理该节点。
-
pop的时机:对某个节点,第二次回溯时,处理并pop
-
后序遍历在实现上有更多的细节需要注意:因为后序遍历中,对每个节点都需要两次回溯,第一次是判断有无right孩子,第二次才是处理该节点。所以需要引入了一个prev指针来记录节点的孩子的处理历史,对访问到的某个节点来说,当访问完它的一棵子树时,用prev指向该子树的父节点,这样当回溯到这个节点的时候,就可以依据prev是指向左子节点,还是右子节点,来判断父节点的访问情况。
分析前中后序迭代法的Uniform写法的异同
同(因为树的遍历规定,访问左子树的顺序优于右子树,所以规定让代码大部分思路是统一的):
- 在循环前的定义中,栈都保留为空,curr为当前访问的节点,指向root
- 循环条件都是判断curr不为空,或栈不为空
- 循环内当left不为空时,都会一直向左访问下去
- 基于上个条件,当curr为空时,会进入循环中的else分支,分支内都会取当前栈顶的元素作为curr
异:
参考上面描述的处理节点值和pop出节点的时机
- 前序和中序,除了对节点的处理时机不同,其他代码都一致
- 后序相对前序和中序,在回溯时多了一步,即判断当前节点的右子树是否已经访问完
////前序遍历--中左右
vector<int> preorderTraversal_uniform(TreeNode *root)
{
vector<int> result;
stack<TreeNode*> sta;
TreeNode* curr = root;
while(curr != nullptr || sta.empty() != true)
{
if (curr != nullptr)
{
result.push_back(curr->val);
sta.push(curr);
curr = curr->left;
}
else
{
curr = sta.top();
sta.pop();
curr = curr->right;
}
}
return result;
}
////中序遍历--左中右
vector<int> inorderTraversal_iteration(TreeNode* root)
{
vector<int> result;
stack<TreeNode*> sta; // 栈中元素代表遍历过的,栈顶表示当前访问的节点
TreeNode* curr = root;
while(curr != nullptr || sta.empty() != true)
{
if(curr != nullptr) // 有左子节点,压入,并将左子节点当作新的当前根节点
{
sta.push(curr);
curr = curr->left;
}
else // 发现没左子节点了,退一步改将当前根节点改回上一步,并pop出,并将其右子节点当作新的当前根节点
{
curr = sta.top();
sta.pop();
result.push_back(curr->val);
curr = curr->right;
}
}
return result;
}
////后序遍历--左右中
vector<int> postorderTraversal_uniform(TreeNode *root)
{
vector<int> result;
stack<TreeNode*> sta;
TreeNode* curr = root;
TreeNode* prev = nullptr;
while (curr != nullptr || sta.empty() != true)
{
if (curr != nullptr)
{
sta.push(curr);
curr = curr->left;
}
else
{
curr = sta.top();
////如果没有右子树,或者右子树访问完了--即上一个访问的节点是右子节点时
////说明可以处理当前节点,处理完后,设置该节点为prev,好让回溯该节点的
////父节点时,知道上步处理了父节点的left还是right孩子。同时要pop()出
////当前节点,在下次重新top()获得的即为回溯后的父节点,更重要的细节是
////不管当前节点对父节点来说是left或right,都需要curr=nullptr,因为
////到了这步,当前节点必定已经处理完了左子树,所以防止在下次循环中再次
////访问左节点,造成死循环
if (curr->right == nullptr || curr->right == prev)
{
result.push_back(curr->val);
prev = curr;
sta.pop();
curr = nullptr;
}
else
{
curr = curr->right;
}
}
}
return result;
}

浙公网安备 33010602011771号