递归遍历
写递归,都按照这三要素来写
确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型(一般都为void)。
确定终止条件: 写完了递归算法,运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
//前序遍历
class Solution {
public:
//递归函数
void preorder(TreeNode* root, vector<int> &a) {
//确认结束条件
if (root == nullptr)return;
//一次循环条件
a.push_back(root->val);
preorder(root->left, a);
preorder(root->right, a);
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> a;
preorder(root, a);
return a;
}
};
//后序遍历
class Solution {
public:
void post(TreeNode* root, vector<int>& a) {
if (root == nullptr)return;
post(root->left, a);
post(root->right, a);
a.push_back(root->val);
}
vector<int> postorderTraversal(TreeNode* root) {
vector<int> a;
post(root, a);
return a;
}
};
此处的void preorder(TreeNode* root, vector<int> &a),“&”表示引用传递,可以确保函数内对
a
的任何修改都直接作用于原始的a
对象,而不是副本。这在递归函数中尤其重要,因为递归函数可能需要在多个递归调用之间共享状态,而不是每次都创建新的副本。【类似于全局变量】
迭代遍历(非递归)
递归的底层逻辑是栈,因此绝大部分递归都可以用栈写出来
前序遍历使用栈,先将根节点入栈(如果根节点为空则直接返回),再出栈,将值加入数组中,判断是否有右孩子,左孩子,分别入栈,再出栈,循环往复
注意是先右孩子,再左孩子,因为栈是倒叙输出的
后序遍历在前序遍历的基础上,更改左右孩子入栈顺序,再将最后结果数组反转
//前序遍历
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> a;
stack<TreeNode*> st;
st.push(root);
if (root == nullptr)return a;
while (!st.empty()) {
TreeNode *cur = st.top();
a.push_back(cur->val);
st.pop();
if (cur->right != nullptr)st.push(cur->right);
if (cur->left != nullptr)st.push(cur->left);
}
return a;
}
};
//后序遍历
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> a;
stack<TreeNode*> st;
st.push(root);
if (root == nullptr)return a;
while (!st.empty()) {
TreeNode *cur = st.top();
a.push_back(cur->val);
st.pop();
if (cur->left != nullptr)st.push(cur->left);
if (cur->right != nullptr)st.push(cur->right);
}
reverse(a.begin(), a.end());
return a;
}
};
分析一下为什么前序遍历的代码,不能和中序遍历通用呢,因为前序遍历的顺序是中左右,先访问(遍历节点)的元素是中间节点,要处理(加入数组)的元素也是中间节点,所以刚刚才能写出相对简洁的代码,因为要访问的元素和要处理的元素顺序是一致的,都是中间节点。
那么再看看中序遍历,中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点(也就是在把节点的数值放进result数组中),这就造成了处理顺序和访问顺序是不一致的。
那么在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。
中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点(也就是在把节点的数值放进result数组中),在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
//先遍历所有的左节点
stack<TreeNode*> st;
TreeNode* cur =root;
vector<int>result;
while (cur != nullptr || !st.empty()) {
if (cur != nullptr) {
st.push(cur);
cur = cur->left;
}
else {
//弹出第一个栈顶元素
cur = st.top();
st.pop();
result.push_back(cur->val);
cur=cur->right;
}
}
return result;
}
};