二叉树遍历
1. 前序遍历
链接:https://leetcode-cn.com/problems/binary-tree-preorder-traversal/
1.1 递归
代码:
class Solution {
public:
vector<int> v;
vector<int> preorderTraversal(TreeNode* root) {
if(root == nullptr)
return {};
v.push_back(root->val);
preorderTraversal(root->left);
preorderTraversal(root->right);
return v;
}
};
1.2 迭代
前序遍历的迭代方式需要借助栈来完成。
- 先将根节点入栈
- 循环判断栈是否为空,不为空时,拿到该栈顶元素,将栈顶元素出栈
- 该元素右子树不为空,先将右子树入栈
- 该元素左子树不为空,再将左子树入栈
- 遍历直到栈为空,则前序遍历完成。
图例:
代码:
class Solution {
public:
vector<int> v;
vector<int> preorderTraversal(TreeNode* root) {
if(root == nullptr)
return {};
stack<TreeNode*> st;
st.push(root);
while(!st.empty())
{
TreeNode* tmp = st.top();//获取栈顶元素,然后出栈
st.pop();
v.push_back(tmp->val);
if(tmp->right != nullptr)//先入右子树
st.push(tmp->right);
if(tmp->left != nullptr)//再入左子树
st.push(tmp->left);
}
return v;
}
};
1.3 morris前序遍历
morris遍历是一种可以将二叉树遍历时空间复杂度变为O(1)的一种遍历方式。
morris前序遍历规则:
- 当前节点左子树为空时,保存当前节点值,并将当前节点置为该节点的右子节点
- 当前节点左子树不为空时,找到当前左子节点的最右节点。
- 如果该最右节点的右子节点为空,将最右节点指向当前节点,并保存当前节点值,当前节点置为左子节点
- 如果该最右节点的右子节点不为空时,将最右节点重新置空,并将当前节点置为其右节点
- 重复1-4步骤,直到当前节点为空。
解释:最右节点的右子节点重新置为空,是为了不改变二叉树本身的结构,否则就改变了二叉树的结构。
图例:
可以先尝试看一下代码,梳理一下逻辑,就能看懂这张图了。
代码:
class Solution {
public:
vector<int> v;
vector<int> preorderTraversal(TreeNode* root) {
if(root == nullptr)
return {};
TreeNode* predecessor = nullptr;
while(root != nullptr)
{
if(root->left != nullptr)//左边不为空
{
predecessor = root->left;
//找到其左子树的最右节点
while(predecessor->right != nullptr && predecessor->right != root)
{
predecessor = predecessor->right;
}
if(predecessor->right == nullptr)//此时为空,将最右节点指向当前节点
{
v.push_back(root->val);//前序遍历,先将值放进去
predecessor->right = root;//改变指向
root = root->left;//当前节点变为其左子节点
}
else//不为空,说明要遍历其右子节点了
{
root = root->right;//改变指向,使其指向右子节点
predecessor->right = nullptr;//恢复原本的树
}
}
else
{
v.push_back(root->val);//没有左子节点,直接将值放入,
root = root->right;//使其等于其右子节点
}
}
return v;
}
};
2.中序遍历
链接:https://leetcode-cn.com/problems/binary-tree-inorder-traversal/
2.1 递归
代码:
class Solution {
public:
vector<int> v;
vector<int> inorderTraversal(TreeNode* root) {
if(root == nullptr)
return {};
inorderTraversal(root->left);
v.push_back(root->val);
inorderTraversal(root->right);
return v;
}
};
2.2 迭代
中序遍历的迭代方法,需要先借助一个栈。
- 循环条件为,栈不空, 或者当前节点不为空
- 循环将当前节点的左子节点入栈,直到当前节点为空
- 获取栈顶元素,出栈
- 保存当前元素,当前节点变为其右子节点
- 重复1-4步骤
图例:
代码:
class Solution {
public:
vector<int> v;
vector<int> inorderTraversal(TreeNode* root) {
if(root == nullptr)
return {};
stack<TreeNode*> st;
while(!st.empty() || root != nullptr)
{
while(root != nullptr)//不为空,元素入栈,再入左边的元素
{
st.push(root);
root = root->left;
}
root = st.top();//获取栈顶元素,然后出栈
st.pop();
v.push_back(root->val);//保存值
root = root->right;//当前节点指向右边
}
return v;
}
};
2.3 morris中序遍历
morris中序遍历和前序遍历步骤大致差不多,只是保存值的位置有所变化。
- 当前节点左子树为空时,保存当前节点值,并将当前节点置为该节点的右子节点
- 当前节点左子树不为空时,找到当前左子节点的最右节点。
- 如果该最右节点的右子节点为空,将最右节点指向当前节点,当前节点置为左子节点
- 如果该最右节点的右子节点不为空时,保存当前值,将最右节点重新置空,并将当前节点置为其右节点
- 重复1-4步骤,直到当前节点为空。
图例
代码:
class Solution {
public:
vector<int> v;
vector<int> inorderTraversal(TreeNode* root) {
if(root == nullptr)
return {};
TreeNode* predecessor = nullptr;
while(root != nullptr)
{
if(root->left != nullptr)
{
predecessor = root->left;
//左不空,寻找最右节点
while(predecessor->right != nullptr && predecessor->right != root)
{
predecessor = predecessor->right;
}
//最右节点右节点为空,将其指向当前节点,当前节点变为其左子节点
if(predecessor->right == nullptr)
{
predecessor->right = root;
root = root->left;
}
else//中序遍历的morris版本是在最右节点不为空时保存当前节点值
{
v.push_back(root->val);
predecessor->right = nullptr;
root = root->right;
}
}
else
{
v.push_back(root->val);
root = root->right;
}
}
return v;
}
};
3.后序遍历
链接:https://leetcode-cn.com/problems/binary-tree-postorder-traversal/
3.1 递归
代码:
class Solution {
public:
vector<int> v;
vector<int> postorderTraversal(TreeNode* root) {
if(root == nullptr)
return {};
postorderTraversal(root->left);
postorderTraversal(root->right);
v.push_back(root->val);
return v;
}
};
3.2迭代
后序遍历的迭代版本比较复杂,具体步骤如下:
- 借助一个临时栈和一个临时指针
- 循环将当前节点的左子节点入栈,直到当前节点为空
- 获取栈顶元素,若是当前元素的右子节点为空,或者右子节点,不等于前驱节点,则保存节点,并且改变其前驱节点,令前驱节点,为当前节点,当前节点置为空
- 如果右子节点不为空,则当前节点置为其右子节点
- 重复步骤2-4,直到栈为空和当前节点为空
图例:
代码:
class Solution {
public:
vector<int> v;
vector<int> postorderTraversal(TreeNode* root) {
if(root == nullptr)
return {};
stack<TreeNode*> st;
TreeNode* prev = nullptr;
while(root != nullptr || !st.empty())
{
while(root != nullptr)//左边元素都先入栈
{
st.push(root);
root = root->left;
}
root = st.top();//获取栈顶元素
if(root->right == nullptr || root->right == prev)//两个条件若有一个满足,则保存节点,改变指向
{
v.push_back(root->val);//保存值
st.pop();//出栈
prev = root;//改变前驱节点指向
root = nullptr;//当前节点置空
}
else
root = root->right;
}
return v;
}
};
3.3 morris后序遍历
morris后序遍历稍显复杂,需要建立一个临时节点,并且令该节点为根节点。并且保存当前左子节点到最右路径上的所节点,并将该几个节点进行逆置。这样就是后序遍历,最后保存的才是根节点。
- 当前节点左子树为空时,保存当前节点值,并将当前节点置为该节点的右子节点
- 当前节点左子树不为空时,找到当前左子节点的最右节点。
- 如果该最右节点的右子节点为空,将最右节点指向当前节点,当前节点置为左子节点
- 如果该最右节点的右子节点不为空时,保存从当前节点的左子节点到该最右节点路径上的所有节点,并且将该路径上的节点进行逆置,将最右节点重新置空,并将当前节点置为其右节点
- 重复1-4步骤,直到当前节点为空。
图例
代码:
class Solution {
public:
vector<int> v;
vector<int> postorderTraversal(TreeNode* root) {
if(root == nullptr)
return {};
TreeNode* newnode = new TreeNode(-1);//新建临时节点
newnode->left = root;
TreeNode* p = newnode;
while(p != nullptr)
{
if(p->left != nullptr)
{
TreeNode* predecessor = p->left;
while(predecessor->right != nullptr && predecessor->right != p)
predecessor = predecessor->right;
if(predecessor->right == nullptr)
{
predecessor->right = p;
p = p->left;
}
else
{
predecessor->right = nullptr;
_postorderTraversal(p->left);//保存节点
p = p->right;//令其等于右子节点
}
}
else
p = p->right;
}
delete newnode;
newnode = nullptr;
return v;
}
private:
void _postorderTraversal(TreeNode* root)
{
int count = 0;
while(root != nullptr)
{
++count;//记录保存了多少个节点,方便逆置
v.push_back(root->val);
root = root->right;
}
reverse(v.end() - count, v.end());//逆置
}
};
4.层序遍历
链接:https://leetcode-cn.com/problems/binary-tree-level-order-traversal/
4.1 递归
代码:
class Solution {
public:
vector<vector<int>> vv;
vector<vector<int>> levelOrder(TreeNode* root) {
if(root == nullptr)
return vv;
_levelOrder(root, 0);
return vv;
}
private:
void _levelOrder(TreeNode* root, int index)
{
if(root == nullptr)
return;
if(index >= vv.size())
vv.push_back(vector<int>());
vv[index].push_back(root->val);
_levelOrder(root->left, index + 1);
_levelOrder(root->right, index + 1);
}
};
4.2 迭代
层序遍历的迭代时,需要借助一个队列。
- 根节点先入队。
- 判断当前队列中有多少个元素,则挨个将元素出队,然后保存当前节点的值。
- 先将队列左子节点入队,再将队列右子节点入队
- 重复2-3步骤,直到队列为空
图例:
代码:
class Solution {
public:
vector<vector<int>> vv;
vector<vector<int>> levelOrder(TreeNode* root) {
if(root == nullptr)
return vv;
queue<TreeNode*> _que;
_que.push(root);
int count = 0;
while(!_que.empty())
{
int sz = _que.size();//队列中有多少个元素
vv.push_back(vector<int>());//每一次进循环,层序遍历都需要多一层
for(int i = 0; i < sz; ++i)//
{
TreeNode* tmp = _que.front();//队头,然后出队
_que.pop();
vv[count].push_back(tmp->val);//保存值
if(tmp->left != nullptr)//左子节点
_que.push(tmp->left);
if(tmp->right != nullptr)//右子节点
_que.push(tmp->right);
}
++count;
}
return vv;
}
};