写在前头:
本文内容部分转载自:https://www.jianshu.com/p/49c8cfd07410 ,本文为笔记整理
首先注意树的层序遍历和非层序遍历区别:
1.二叉树的前中后序遍历是递归遍历,需要用栈。(深度优先)
2.二叉树的层序遍历使用队列。(广度优先)
首先来看二叉树的三种遍历递归写法:
三种递归遍历对遍历的描述,思路非常简洁,最重要的是三种方法完全统一,大大减轻了我们理解的负担。而我们常接触到那三种非递归遍历方法,除了都使用栈,具体实现各有差异,导致了理解的模糊。
//前序遍历
void preorder(TreeNode *root, vector<int> &path)
{
if(root != NULL)
{
path.push_back(root->val);
preorder(root->left, path);
preorder(root->right, path);
}
}
//中序遍历
void inorder(TreeNode *root, vector<int> &path)
{
if(root != NULL)
{
inorder(root->left, path);
path.push_back(root->val);
inorder(root->right, path);
}
}
//后续遍历
void postorder(TreeNode *root, vector<int> &path)
{
if(root != NULL)
{
postorder(root->left, path);
postorder(root->right, path);
path.push_back(root->val);
}
}
接下来看千奇百怪的非递归:
一、前序遍历:
leetcode144题中有很多优秀讨论其中使用了下述二叉树非递归前序遍历方法:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res;
if(!root) return res;
stack<TreeNode*> s;
s.push(root);
while(!s.empty()){
TreeNode* node = s.top();
s.pop();
res.push_back(node->val);
if(node->right) //后处理右子树,所以右子树先入栈
s.push(node->right);
if(node->left) //先处理左子树,所以左子树后入,总是得到最新的左子树
s.push(node->left);
}
return res;
}
算法不难理解,依然是根----左-----右,唯一的小技巧是在压栈时先将右子树压入栈中,所以当有左子树时就可以不处理右子树。
教科书中的非递归前序遍历(代码依然来自上述连接):
//非递归前序遍历
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> s;
while(root || !s.empty()){
while(root){
res.push_back(root->val);
s.push(root);
root = root->left; //模拟递归到最左边,循环代替
} //当左空时,进行后续操作
if(!s.empty()){ //如果栈中有待处理元素,检查是否有右子树,用栈模拟递归销毁过程
root = s.top();
s.pop();
root = root->right;
}
}
return res;
}
其实就是利用栈模拟递归和递归销毁的返回过程。比上一个方式略微耗时(两层循环)。而且 不要将循环里的if(!s.empty())去掉,去掉后更加耗时。
二、中序遍历
题目位于leetcode94
教科书式解法
//非递归中序遍历
vector<int> inorderTraversal(TreeNode* root) {
stack<TreeNode*> s;
vector<int> res;
while(root || !s.empty()){
while(root){ //先进到最左
s.push(root);
root = root->left;
} //此时已经位于最左端
if(!s.empty()){
root = s.top();
res.push_back(root->val);
s.pop();
root = root->right;
}
}
return res;
}
三、后序遍历
题目位于leetcode145
注意:后序遍历与前序中序最大的区别是后续遍历需要一个标记来记录上一个右子树是否已经被访问过,因为递归的过程(压栈)是先压根节点再压左右节点,所以如果不做标记,每当访问根节点,右子树会一直被压栈,然后进入死循环,所以需要一个标记来记录右子树是否被访问过。
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> s;
TreeNode* visitedRight = nullptr;
while(root || !s.empty()){
if(root){ //先进入左
s.push(root);
root = root->left;
}
else{ //当已经没有左子树的时候,要进入右子树,是否进入使用标记判断
root = s.top();
if(root->right && root->right != visitedRight){
//当右子树没有被访问过时,进入右子树
root = root->right;
}
else{ //否则 直接对当前节点进行访问
res.push_back(root->val);
s.pop();
visitedRight = root;
root = nullptr; //记得清空root 否则下一次循环会陷入无限压栈状态
}
}
}
return res;
}
四、层序遍历
最后层序遍历代码:题目见leetcode102, 使用队列即可
vector<vector<int>> levelOrder(TreeNode* root) {
queue<TreeNode*> q;
vector<vector<int> > res2d;
if(!root)
return res2d;
q.push(root);
while(!q.empty()){
vector<int> res1d;
int levelNum = q.size();
for(int i = 0; i < levelNum; i++){
TreeNode* p = q.front();
q.pop();
res1d.push_back(p->val);
if(p->left)
q.push(p->left);
if(p->right)
q.push(p->right);
}
res2d.push_back(res1d);
}
return res2d;
}