LeetCode【学习计划】:【数据结构】
题目
144. 二叉树的前序遍历
LeetCode: 144. 二叉树的前序遍历
简 单 \color{#00AF9B}{简单} 简单
94. 二叉树的中序遍历
LeetCode: 94. 二叉树的中序遍历
简 单 \color{#00AF9B}{简单} 简单
145. 二叉树的后序遍历
LeetCode: 145. 二叉树的后序遍历
简 单 \color{#00AF9B}{简单} 简单
前言
前序遍历:根节点
->左孩子->右孩子
中序遍历:左孩子->根节点
->右孩子
后序遍历:左孩子->右孩子->根节点
方法1:递归
树的遍历过程中,在进入到左孩子和右孩子后,还需要按照同样的遍历顺序去遍历,直到遍历整棵树,因此遍历的过程是一个非常典型的递归过程。
递归函数中事件的排列:
前序
do something
- preorder(root->
left
);- preorder(root->
right
);
中序
- inorder(root->
left
);do something
- inorder(root->
right
);
后序
- postorder(root->
left
);- postorder(root->
right
);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)。主要为显式栈的空间开销。