二叉树的深度优先遍历包括前序遍历(依次访问中、左、右节点)、中序遍历(依次访问左、中、右节点)、后序遍历(依次访问左、右、中节点)。今天主要学习这三种遍历的实现方法
目录
一、递归遍历
二、迭代遍历
三、迭代的统一写法
这是Leetcode上三道题的链接:
Leetcode 144.二叉树的前序遍历
Leetcode 94.二叉树的中序遍历
Leetcode 145.二叉树的后序遍历
如果对二叉树的基础理论知识不了解的可以先看这里:代码随想录
题目描述:给你二叉树的根节点 root
,返回它节点值的 前/中/后序 遍历。
思路:这三道题的递归写法只有细微差别,原因是不同遍历方法的访问节点的顺序不同。
实现递归,可以按照以下三步来思考:
1.确定递归函数的参数和返回值
2.确定终止条件
3.确定单层递归的逻辑
根据这三步,我们来思考一下如何利用递归来实现二叉树的深度遍历。
1.根据题意需要返回节点的数值,因此需要传入一个vector存放最终所有的数值,而不需要返回值
2.当遍历到空节点的时候,本层递归结束,返回上一级
3.前/中/后序需要做的内容相同,只不过顺序不同。
代码如下:(递归)
前序遍历:
class Solution {
public:
void preorder(TreeNode* cur, vector& vec) {
if (cur == nullptr)
return;
vec.push_back(cur->val);//中
preorder(cur->left, vec);//左
preorder(cur->right, vec);//右
}
vector preorderTraversal(TreeNode* root) {
vector result;
preorder(root, result);
return result;
}
};
中序遍历:
class Solution {
public:
void inorder(TreeNode* cur, vector& vec) {
if (cur == nullptr)
return;
inorder(cur->left, vec);//左
vec.push_back(cur->val);//右
inorder(cur->right, vec);//中
}
vector inorderTraversal(TreeNode* root) {
vector result;
inorder(root, result);
return result;
}
};
后序遍历:
class Solution {
public:
void postorder(TreeNode* cur, vector& vec) {
if (cur == nullptr)
return;
postorder(cur->left, vec);//左
postorder(cur->right, vec);//右
vec.push_back(cur->val);//中
}
vector postorderTraversal(TreeNode* root) {
vector result;
postorder(root, result);
return result;
}
};
思路:由于递归的底层实现是栈,因此即使用迭代的方法,原理还是栈。这里需要注意的一点是栈的特点是先进后出,因此迭代遍历和递归遍历的节点访问写法正好相反。举个例子:前序遍历是中左右,因此入栈的顺序是右左中。其实理解了递归,迭代并不难写。
前序遍历:
class Solution {
public:
vector preorderTraversal(TreeNode* root) {
stack st;
vector result;
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)
st.push(node->right);
if (node->left)
st.push(node->left);
}
return result;
}
};
后序遍历:
class Solution {
public:
vector postorderTraversal(TreeNode* root) {
stack st;
vector result;
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)
st.push(node->left);
if (node->right)
st.push(node->right);
}
reverse(result.begin(), result.end());
return result;
}
};
不过利用上述思路我们做题时会发现,前序遍历和后序遍历似乎可以这样解决,但是中序遍历却不能,这是为什么呢?仔细分析之后我们发现:
迭代的过程包括了两个过程:(1)访问:遍历节点(2)处理:将元素放进result数组中
而正是因为前、后序遍历访问的元素和要处理的元素顺序一致,所以可以用上述思路。但中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点(也就是在把节点的数值放进result数组中),这就造成了处理的元素顺序和访问元素的顺序是不一致的。
因此我们需要借助指针来遍历访问节点,而栈用来处理元素。
中序遍历:
class Solution {
public:
vector inorderTraversal(TreeNode* root) {
stack st;
vector result;
TreeNode* cur = root;
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;
}
};
思路:既然上面那个迭代遍历对于不同的遍历方式可能会有不同的迭代方式,那有没有一种“一劳永逸”的方法呢?当然有,接下来介绍一种可以统一三种遍历的迭代方法。这种方法主要的原理是:我们除了将访问的节点放入栈中,也把要添加到result的节点也放入栈中,只不过在该节点的上方放入一个空指针作为标记,这样通过栈,我们不仅可以遍历访问所有节点,也可以将节点内元素值添加到result中。动画演示看这里
解题步骤:
(1)先将非空的根节点加入到栈.
(2)只要栈不为空,判断栈顶元素是否为空。
(3)如果栈顶元素不为空,说明该元素没被访问,需要先访问,并根据前/中/后序的遍历方法将节点依次放入栈中;否则先将空节点弹出,并将栈顶元素取出放入result中,之后弹出该节点。
代码如下:
前序遍历
class Solution {
public:
vector preorderTraversal(TreeNode* root) {
vector result;
stack st;
if (root != nullptr)
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
if (node != nullptr) {
st.pop();
if (node->right)
st.push(node->right);
if (node->left)
st.push(node->left);
st.push(node);
st.push(nullptr);
} else {
st.pop();
node = st.top();
result.push_back(node->val);
st.pop();
}
}
return result;
}
};
中序遍历
class Solution {
public:
vector inorderTraversal(TreeNode* root) {
vector result;
stack st;
if (root != nullptr)
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
if (node != nullptr) {
st.pop();
if (node->right)
st.push(node->right);
st.push(node);
st.push(nullptr);
if (node->left)
st.push(node->left);
} else {
st.pop();
node = st.top();
st.pop();
result.push_back(node->val);
}
}
return result;
}
};
后序遍历
class Solution {
public:
vector postorderTraversal(TreeNode* root) {
vector result;
stack st;
if (root != nullptr)
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
if (node != nullptr) {
st.pop();
st.push(node);
st.push(nullptr);
if (node->right)
st.push(node->right);
if (node->left)
st.push(node->left);
} else {
st.pop();
node = st.top();
result.push_back(node->val);
st.pop();
}
}
return result;
}
};
总结:二叉树的深度优先遍历很重要,尤其是用递归和迭代实现的写法。
最后,如果文章有错误,请在评论区或私信指出,让我们共同进步!