【LeetCode学习计划】《数据结构入门-C++》第10天 树

LeetCode【学习计划】:【数据结构】



题目

144. 二叉树的前序遍历

LeetCode: 144. 二叉树的前序遍历

简 单 \color{#00AF9B}{简单}


94. 二叉树的中序遍历

LeetCode: 94. 二叉树的中序遍历

简 单 \color{#00AF9B}{简单}


145. 二叉树的后序遍历

LeetCode: 145. 二叉树的后序遍历

简 单 \color{#00AF9B}{简单}



前言

前序遍历:根节点->左孩子->右孩子

中序遍历:左孩子->根节点->右孩子

后序遍历:左孩子->右孩子->根节点


方法1:递归

树的遍历过程中,在进入到左孩子和右孩子后,还需要按照同样的遍历顺序去遍历,直到遍历整棵树,因此遍历的过程是一个非常典型的递归过程。

递归函数中事件的排列:

前序

  1. do something
  2. preorder(root->left);
  3. preorder(root->right);

中序

  1. inorder(root->left);
  2. do something
  3. inorder(root->right);

后序

  1. postorder(root->left);
  2. postorder(root->right);
  3. do something

递归中事件的排列非常符合我们对于遍历过程的直观印象。

144. 二叉树的前序遍历为例,代码如下:

#include <vector>
using namespace std;
class Solution
{
public:
    vector<int> preorderTraversal(TreeNode *root)
    {
        vector<int> ans;
        preorder(root, ans);
        return ans;
    }
    void preorder(TreeNode *root, vector<int> &ans)
    {
        if (!root)
            return;
        ans.push_back(root->val);
        preorder(root->left, ans);
        preorder(root->right, ans);
    }
};

对于其它的两题,只需要改变递归函数中事件的顺序即可。

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n)n 为树中的结点数,所有结点都会遍历一次。

  • 空间复杂度: O ( n ) O(n) O(n)。主要为递归过程中栈的开销。平均情况下为 O ( log ⁡ n ) O(\log n) O(logn);最坏情况下树呈链状达到 O ( n ) O(n) O(n)


方法2:迭代

我们可以把递归过程中隐式维护的栈具象出来。


对于前序遍历,我们在进入任意子树时,需要先对当前结点操作 doSomething(),然后访问它的左孩子 p = p->left;如果左孩子不是空结点,则要进入以左孩子为根节点的子树,重复操作while(p)。因此从栈中拿出一个根节点后,先要一直往左走,直到左孩子为空后,才访问右孩子。同理,进入右孩子后也要将它作为根节点往左进发。详见如下代码结构。

if (!root)
    return {};

TreeNode *p = root;
stk.push(root);
while (!stk.empty())
{
    while (p)
    {
        doSomething();
        stk.push(p);
        p = p->left;
    }
    p = stk.top();
    stk.pop();
    p = p->right;
}

对于中序遍历,我们则要先一直往左走p = p->left,直到左孩子为空后,再对当前结点操作doSomething(),然后紧接着就是访问右孩子p = p->right。详见如下代码结构。

if (!root)
    return {};

TreeNode *p = root;
while (!stk.empty())
{
    while (p)
    {
        stk.push(p);
        p = p->left;
    }
    p = stk.top();
    stk.pop();
    doSomething();
    p = p->right;
}

对于后序遍历,它的代码会稍微多一点。因为在一直往左走后p = p->left(每次往左走之前将根节点压入栈),需要再从栈顶拿出根节点stk.pop(),先去访问右孩子p = p->right,将右孩子压入栈后进行以它为根节点的新一轮循环。

假设右孩子的子树遍历完了(不管如何就是遍历完了),现在回到了刚刚的根节点。但此时根节点有右孩子,这一次又会把右孩子压入栈,然后去访问右孩子的子树。所以我们需要一个额外的变量lastRight。当右孩子遍历完成后(在右孩子子树中,右孩子为p),设lastRight=p。从右孩子回来后,根节点为p,右孩子为p->right,这时我们判断一次p->right是否等于lastRight如果等于就不用进入右孩子。

右孩子遍历完成,是时候对根节点操作了doSomething()。然后就直接从栈中再取结点。因为所有根节点的左孩子和右孩子已经在之前就压入栈了。

if (!root)
    return {};

TreeNode *p = root, *lastRight = nullptr;
while (p || !stk.empty())
{
    while (p)
    {
        stk.push(p);
        p = p->left;
    }
    p = stk.top();
    stk.pop();
    if (!p->right || p->right == lastRight)
    {
        ans.emplace_back(p->val);
        lastRight = p;
        p = nullptr;
    }
    else
    {
        stk.push(p);
        p = p->right;
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n)n 为树中的结点数,所有结点都会遍历一次。

  • 空间复杂度: O ( n ) O(n) O(n)。主要为显式栈的空间开销。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

亡心灵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值