二叉树的递归遍历
题干
二叉树的前序遍历
题目:给你二叉树的根节点 root ,返回它节点值的 前序 遍历。
二叉树的中序遍历
题目:给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。
二叉树的后序遍历
题目:给你一棵二叉树的根节点 root ,返回其节点值的 后序遍历 。
思路
要用递归法写前中后序三种遍历,那得先搞清楚递归是什么,怎么写递归。
递归是什么
递归(Recursion)就是在函数运行的过程中调用自己。递归通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,通过函数自我调用的方式,将问题重复地分解为同类子问题,并最终解决问题。
递归的三要素
-
确定递归函数的参数和返回值: 确定参数是哪些和返回值的类型。
-
确定递归的终止条件: 如果没有写终止条件或者终止条件书写错误,就会遇到栈溢出的错误。操作系统用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
-
确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
代码
前序遍历递归
class Solution {
public:
vector<int> result; // result可不可以放在递归函数里?不可以
vector<int> preorderTraversal(TreeNode* root) {
// 中左右
// 递归结束的条件是什么?传递进来了空指针
if (root == nullptr){
return result;
}
result.push_back(root->val);
preorderTraversal(root->left);
preorderTraversal(root->right);
return result;
}
};
中序遍历递归
class Solution {
public:
vector<int> result;
vector<int> inorderTraversal(TreeNode* root) {
if (root == nullptr){
return result;
}
// 左中右
inorderTraversal(root->left);
result.push_back(root->val);
inorderTraversal(root->right);
return result;
}
};
后序遍历递归
class Solution {
public:
vector<int> result;
vector<int> postorderTraversal(TreeNode* root) {
if (root == nullptr){
return result;
}
// 左右中
postorderTraversal(root->left);
postorderTraversal(root->right);
result.push_back(root->val);
return result;
}
};
二叉树的迭代遍历
题干
二叉树的前序遍历
题目:给你二叉树的根节点 root ,返回它节点值的 前序 遍历。
二叉树的中序遍历
题目:给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。
二叉树的后序遍历
题目:给你一棵二叉树的根节点 root ,返回其节点值的 后序遍历 。
思路
用迭代法解决二叉树前中后序的遍历问题,重点在于理解为什么要用栈来实现,栈到底存储了什么关键信息?
共同点
无论是前中后序遍历,都需要明确 被遍历的结点 和 需要处理的结点 是不同的。(需要被处理的指需要插入序列数组中的结点)
当前遍历到的元素不一定就是现在要处理,很可能是要等遍历完其他结点才后处理,那么关键的问题就在于,我们遍历完其他结点时,如何又回到这个结点呢?此时就要运用到栈这个数据结构,让栈存储 遍历过的而又还不需要被处理的 结点,当需要回溯时优先会返回距离更近的结点,而距离更近的结点肯定是最后才存储的,和栈的后进先出的特性相符合。在递归法中,本质上就是栈空间存储了返回上一个结点的信息。
前序遍历:根据中左右的顺序遍历,遍历到的结点就是要处理的结点,当前节点的右子树结点需要被暂存起来。
中序遍历:根据左中右的原则,优先处理最左边的结点,因此需要不断向左遍历,只要不是最左边的结点,其余结点都需要暂存起来。
后序遍历:根据左右中的原则,可以先修改前序遍历存储 “中右左” 序列,最后再将数组翻转为 “左右中”。如果是“中右左”,此时需要不断往右遍历,当前结点的左子树结点需要先被暂存起来。
前序遍历
按照中左右的顺序,由于左结点在右结点前输出,cur 指针优先向左遍历。同时当前 cur 遍历到的元素就是我们要处理的元素, 可以直接把 cur 结点的值插入序列数组中。若当前结点有右孩子,则需要让右孩子入栈。之后不断让 cur 向左遍历,如果 cur 为空指针,说明左子树遍历完毕,需要回溯到最近的上一个结点,此时就需要弹栈,让 cur 指向栈顶结点。
中序遍历
按照左中右的顺序,我们应该优先处理最左边的结点。所以当遍历到的结点不是最左边的结点时,我们需要暂存到栈里。也就是要不断让 cur = cur->left,如果 cur 遍历到空结点,说明已经到了最左边,此时就需要回溯弹栈,让 cur = stack.top()。回到上一个结点后,让当前结点值插入序列数组,根据左中右的顺序,我们接下来应该遍历右子树,因此让 cur = cur->right。
后序遍历
后序遍历是左右中,逆序是中右左,我们已经知道前序遍历的写法,其实只需要将前序遍历的代码修改为中右左的顺序遍历,最后再将数组翻转就可以得到左右中的后序遍历序列。
代码
前序遍历:迭代法
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> tmpNode; // 栈按顺序存储了需要遍历的结点
TreeNode* cur = root; // 遍历二叉树
while (cur != nullptr || !tmpNode.empty()){
if (cur != nullptr){
// 要处理的元素就是当前遍历的结点
result.push_back(cur->val);
// 中左右的顺序,先让右边结点入栈
if (cur->right != nullptr){
tmpNode.push(cur->right);
}
cur = cur->left;
} else{
// cur 为空,说明左子树已经遍历完,要回溯到上一个结点
cur = tmpNode.top();
tmpNode.pop();
}
}
return result;
}
};
中序遍历:迭代法
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
stack<TreeNode*> tmpNode;
TreeNode* cur = root;
vector<int> result;
while (cur != nullptr || !tmpNode.empty()){
if (cur == nullptr){
// cur 为空说明已经找到最左的结点,需要回溯到上一个结点
cur = tmpNode.top();
tmpNode.pop();
result.push_back(cur->val);
cur = cur->right; // 开始遍历右子树
} else{
// 让 cur 不断往左子树遍历,直到找到最左结点,其余结点存入栈中
tmpNode.push(cur);
cur = cur->left;
}
}
return result;
}
};
后序遍历:迭代法
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> tmpNode; // 栈按顺序存储了需要遍历的结点
TreeNode* cur = root; // cur 遍历二叉树所有结点
while (cur != nullptr || !tmpNode.empty()){
if (cur != nullptr){
// 要处理的元素就是当前遍历的结点
result.push_back(cur->val);
// 中右左的顺序,先让左边结点入栈
if (cur->left != nullptr){
tmpNode.push(cur->left);
}
cur = cur->right; // 不断往右遍历
} else{
// cur 为空,说明右子树已经遍历完,要回溯到上一个结点
cur = tmpNode.top();
tmpNode.pop();
}
}
reverse(result.begin(),result.end());
return result;
}
};
其他思路
这个思路只适用于前序、后序遍历,并不适用中序遍历。因为前序遍历中要处理的节点和遍历的节点相同,此时就可以用栈先按 “右左中” 顺序存储结点,(输出是“中左右”,插入时需要反过来),我们只需要按照栈中的结点顺序遍历元素并插入序列数组即可。
题解:代码随想录
前序遍历:迭代法二
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
if (root == nullptr){
return result;
}
stack<TreeNode*> tmpNode; // 栈按顺序存储了需要遍历的结点
tmpNode.push(root);
TreeNode* cur; // 遍历二叉树
while (!tmpNode.empty()){
// 遍历栈中的结点
cur = tmpNode.top();
result.push_back(cur->val);
tmpNode.pop();
// 先在栈中插入右子树结点,再插入左子树结点
if (cur->right != nullptr) tmpNode.push(cur->right);
if (cur->left != nullptr) tmpNode.push(cur->left);
}
return result;
}
};
后序遍历:迭代法二
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> result;
if (root == nullptr){
return result;
}
stack<TreeNode*> tmpNode; // 存储右边结点和中间结点
tmpNode.push(root);
TreeNode* cur;
// 左右中 其实就是 中右左 的逆序,只需按中右左遍历,最后翻转即可
while (!tmpNode.empty()){
cur = tmpNode.top();
tmpNode.pop();
result.push_back(cur->val);
// 先让左子树结点入栈,再让右子树结点入栈,这样输出的时候就是右节点先出
if (cur->left != nullptr){
tmpNode.push(cur->left);
}
if (cur->right != nullptr){
tmpNode.push(cur->right);
}
}
reverse(result.begin(),result.end());
return result;
}
};