1.递归遍历
题目链接/文章讲解/视频讲解: 代码随想录
代码:
class Solution {
public:
void Traversal(TreeNode* cur,vector<int>& result){
// 如果将递归函数中的参数 vector<int>& result 改为 vector<int> result,会导致在递归调用的过程中创建了 result 的副本,而不是对同一个 result 的引用。这意味着在递归调用结束后,每次递归调用中的 result 都会被销毁,因此最终返回的结果将是空的。
if(cur == NULL){
return;
}
result.push_back(cur->val);
Traversal(cur->left,result);
Traversal(cur->right,result);
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
Traversal(root,result);
return result;
}
};
代码:
class Solution {
public:
void Traversal(TreeNode* cur,vector<int>& result){
if(cur == nullptr){
return;
}
Traversal(cur->left,result);
Traversal(cur->right,result);
result.push_back(cur->val);
}
vector<int> postorderTraversal(TreeNode* root) {
vector<int> result;
Traversal(root,result);
return result;
}
};
代码:
class Solution {
public:
void traversal(TreeNode* cur,vector<int>& result){
if(cur == nullptr){
return;
}
traversal(cur->left,result);
result.push_back(cur->val);
traversal(cur->right,result);
}
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
traversal(root,result);
return result;
}
};
思路:
递归三部曲:确定递归参数和返回值,确定递归终止条件,确定递归处理逻辑。
2.二叉树的迭代遍历
题目链接/文章讲解/视频讲解: 代码随想录
前序:
代码:
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
if(root == nullptr) return result;
st.push(root);
while(!st.empty()){
// 中
TreeNode* node = st.top();
st.pop();
result.push_back(node->val);
// 右
if(node->right != nullptr) st.push(node->right);
// 左
if(node->left != nullptr) st.push(node->left);
}
return result;
}
};
思路:
用迭代来模拟递归过程离不开栈。 为了实现前序遍历,即中左右,我们先处理中间结点,然后将其右孩子压入栈,再将其左孩子压入栈(考虑到栈先进后出)。
整个代码实现了空结点不入栈。
后序:
代码:
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
if(root == nullptr) return result; // 空结点不入栈
st.push(root);
while(!st.empty()){
// 中
TreeNode* node = st.top();
st.pop();
result.push_back(node->val);
// 左
if(node->left != nullptr) st.push(node->left);
// 右
if(node->right != nullptr) st.push(node->right);
}
reverse(result.begin(),result.end());
return result;
}
};
思路:
刚刚实现了前序遍历,即中左右。那么我们只要在此基础上,将左右反一下 ,得到中右左。最后将数组翻转,就可以得到后序遍历,左右中。
中序:
代码:
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
if(root == nullptr) return result;
TreeNode* cur = root; // 用cur指针来访问结点,用它的值来判断什么时候该处理结点了
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;
}
};
思路:
因为中序遍历里的访问顺序和处理的顺序是不一样的(所以不能用上一种解法调换顺序就可以达到)。所以我们借助了cur指针来遍历左节点,直到左孩子为空,再开始处理该节点,并在之后将右节点压入栈。
这样还要想着访问顺序和处理顺序也太麻烦了!!有没有那种和递归法一样,只要调换下顺序就都能用的!! 统一迭代来了!!
3.二叉树的统一迭代(标记法)
题目链接/文章讲解: 代码随想录
前序:
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
if(root != NULL) st.push(root);
while(!st.empty()){
TreeNode* cur = st.top();
if(cur != NULL){ // 将访问过的结点按照右左中的顺序压入栈中(栈先进后出)
st.pop(); // 将中结点弹出,换成右左中
// 右(空结点不入栈)
if(cur->right != NULL) st.push(cur->right);
// 左(空结点不入栈)
if(cur->left != NULL) st.push(cur->left);
// 中
st.push(cur);
st.push(NULL); // 标记为:“要处理的结点”(没有标记的还需继续去访问它的左右结点,看看有没有遗漏的)
}else{ // 处理 标记为“要处理的结点”
st.pop(); // 先将空结点(或标记)弹出
cur = st.top();
st.pop();
result.push_back(cur->val);
}
}
return result;
}
};
后序:
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
if(root != NULL) st.push(root);
while(!st.empty()){
TreeNode* cur = st.top();
if(cur != NULL){ // 将访问过的结点按照中右左的顺序压入栈中(栈先进后出)
st.pop(); // 将中结点弹出,换成中右左
// 中
st.push(cur);
st.push(NULL); // 标记为:“要处理的结点”(没有标记的还需继续去访问它的左右结点,看看有没有遗漏的)
// 右(空结点不入栈)
if(cur->right != NULL) st.push(cur->right);
// 左(空结点不入栈)
if(cur->left != NULL) st.push(cur->left);
}else{ // 处理 标记为“要处理的结点”
st.pop(); // 先将空结点(或标记)弹出
cur = st.top();
st.pop();
result.push_back(cur->val);
}
}
return result;
}
};
中序:
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
if(root != NULL) st.push(root);
while(!st.empty()){
TreeNode* cur = st.top();
if(cur != NULL){ // 将访问过的结点按照右左中的顺序压入栈中(栈先进后出)
st.pop(); // 将中结点弹出,换成右左中
// 右(空结点不入栈)
if(cur->right != NULL) st.push(cur->right);
// 中
st.push(cur);
st.push(NULL); // 标记为:“要处理的结点”(没有标记的还需继续去访问它的左右结点,看看有没有遗漏的)
// 左(空结点不入栈)
if(cur->left != NULL) st.push(cur->left);
}else{ // 处理 标记为“要处理的结点”
st.pop(); // 先将空结点(或标记)弹出
cur = st.top();
st.pop();
result.push_back(cur->val);
}
}
return result;
}
};
思路:
使用栈的话,无法同时解决访问节点(遍历节点)和处理节点(将元素放进结果集)不一致的情况。
那我们就将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记。
如何标记呢,就是要处理的节点放入栈之后,紧接着放入一个空指针作为标记。 这种方法也可以叫做标记法。
遇到NULL就处理结点;遇到不是NULL的情况,就 访问结点(访问顺序和要求的遍历顺序相反,因为栈先进后出)
这种思路还是得自己画图模拟一遍,思路难,代码还是好写的。