美文网首页
*二叉树的遍历总结

*二叉树的遍历总结

作者: lazy_ccccat | 来源:发表于2020-03-08 13:09 被阅读0次

首先来复习一下三种遍历方式

所谓二叉树的遍历,是指按照一定的搜索路径将树的所有结点访问且仅访问一次。
按照根结点何时被访问,可以分为先序、中序和后序三种遍历算法.
主要区别在于 root 节点什么时候被访问.

先序:访问根结点,先序遍历左子树,先序遍历右子树
中序:中序遍历左子树、访问根结点、中序遍历右子树
后序:后序遍历左子树、后序遍历右子树、访问根结点


image.png

先序

https://leetcode-cn.com/problems/binary-tree-preorder-traversal/

递归

class Solution {
public:
    vector<int> res;
    vector<int> preorderTraversal(TreeNode* root) {
        if (!root) return res;
        res.push_back(root->val);
        preorderTraversal(root->left);
        preorderTraversal(root->right);
        return res;
    }
};

非递归

思路

首先访问当前树的根结点数据,接下来应该依次遍历其左子树和右子树,然而程序的控制流只能处理其一,所以考虑将右子树的根保存在栈里面,当前指针则指向需先处理的左子树,为下次循环做准备;若当前指针指向的树为空,说明当前树为空树,不需要做任何处理,直接弹出栈顶的子树,即遍历右子树。

伪代码

void preOrder(TreeNode root)
{
    Init(stack);
    //tips:初始栈压入一个null,pop的时候就不用对这个栈判空了
    stack.push(null); 
    while( root != null ) 
    {
        visit(root);
        if(root.right != null) stack.push(root.right);
        if(root.left != null) root = root.left;
        else root = stack.pop();
    }
}

代码

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*> st;
        vector<int> res;
        st.push(NULL);
        while (root) {
           res.push_back(root->val);
           if (root->right) st.push(root->right);
           if (root->left) root = root->left;
           else {
               root = st.top();
               st.pop();
           }
        }
        return res;
    }
};

总结

  1. 由于是先访问根节点,再遍历左子树,因此root = root.left的同时要把root.right存到栈里,还不能遍历右子树,因为左子树还没有遍历完(左子树为null)。所以,直到root.left == null的时候,才可以遍历右子树(root = stack.pop())。
    stack在pop前要判断这个栈是否为空:stack.isEmpty()
  2. 不想显式地写这个判断的话,可以使用一个tip:初始化栈的时候push进去一个null值。这样就可以直接pop了,如果栈为空了,pop出来一个null值,正好。
  3. visit(root)的含义视情况而定。可以为打印(对于函数返回值为void)。
    本题为返回一个list,所以visit(root)的含义是list.add(root)。
  4. 注意C++中stack,其中有两个方法:

pop(), 返回void,
top(),返回栈顶的引用。
所以想要提取栈顶元素,直接用s.top()

中序

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> st;
        while (root || !st.empty()) {
            if (root) {
                st.push(root);
                root = root->left;
            } else {
                root = st.top();
                res.push_back(root->val);
                st.pop();
                root = root->right; 
            }
        }
        return res;
    }
};

后序

这个改天再自己做一下,大致意思写对了,小细节没写对,导致死循环。我也懒得debug了直接照着答案改了。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> st;
        TreeNode* r;//记录刚访问过的节点
        while (root || !st.empty()) {
            if (root) {
                st.push(root);
                root = root->left;
            }
            // root为null 说明左孩子已经没得访问了,可能要访问右孩子,也可能右孩子也访问完了
            else {
                // 如果存在右孩子,而且右孩子不是上次最后访问的那个节点
                // 那么说明root 的右孩子还没有访问,那么栈顶的右孩子置成 root, 继续遍历
                TreeNode* tmp = st.top();
                if (tmp->right && tmp->right != r) {
                    root = tmp->right;
                } else {
                // 其他情况,例如右孩子NULL,或者 右孩子是上次访问的节点(说明栈顶这个节点的右孩子已经访问过了)
                // 那么直接访问栈顶这个节点即可,还要弹栈
                    res.push_back(tmp->val);
                    st.pop();
                    r = tmp; //记录刚访问过的节点
                }
            }
        }
        return res;
    }
};

写了三种遍历方式之后,突然意识到,为什么非递归一定会用到栈?
我觉得是因为,二叉树的结构,父结点到子结点的指针是单向的,没有从子结点指向父结点的指针。因此如果从孩子结点回到父结点,只有一个办法就是弹栈。

好,回到正题。
后序遍历的顺序是"左子树->右子树->根",它要求左子树和右子树都在根之前输出。
所以后序遍历序列有个特点,visit到某个结点时,其左右儿子一定刚刚被visit过(如果有儿子的话)。也就是说,父结点前面紧挨着的一定是儿子结点(如果有的话)。


image.png

后序:b c a
而从整体来看,后序遍历是从下往上遍历的。怎么理解呢?就是假设当前访问的是a,那么b,c子树一定都被访问过了。也就是a的下层所有结点都已经被访问过了。
假设当前访问到b,那么说明b子树已经遍历过了,下面要遍历b的右兄弟,如何从b到c呢?显然是b->a然后a->c。即依赖b的父结点作为桥梁。

当访问某个结点p时,栈中的结点恰好是p的所有祖先。

所以访问完b之后,要指向a,a在哪里?在栈顶。
此时不要立刻访问a,而是要看看a还有没有右子树,毕竟左右子树都遍历完成之后才可以访问根结点a。所以不要弹栈,而是p = top()去让p指向a,看看a.right是否存在并且没访问过。
用栈来存储结点,必须分清返回根结点时(s.top()),是从左子树返回的,还是从右子树返回的。因此用一个辅助指针r指向一个最近访问过的结点。
例如,访问完b之后,r = b纪录一下, p = top()查看栈顶,然后看p.right是否存在并且p.right是否不等于r,发现c != b,ok可以继续遍历右子树c。
右子树c遍历完成的标志事件是访问c,这个时候你不知道c是不是父结点的右儿子呀,所以p = top(), 然后看看p.right是否存在并且等于c,发现等于,说明右子树已经遍历过了,此时可以访问根结点了,弹栈吧。

还有颜色标记法 据说很简单 值得研究
https://leetcode-cn.com/problems/binary-tree-inorder-traversal/solution/yan-se-biao-ji-fa-yi-chong-tong-yong-qie-jian-ming/

相关文章

  • 算法-二叉树算法总结

    二叉树算法总结 1 二叉树的遍历 1.1 前序遍历 递归 迭代 1.2 中序遍历 递归 迭代 1.3 后序遍历 递...

  • 二叉树遍历

    总结一下二叉树的深度遍历(DFS)和广度遍历(BFS)首先, 创建二叉树的节点: 一、深度遍历 1.1 先序遍历(...

  • 二叉树的遍历

    二叉树的遍历 前序遍历 访问根结点 前序遍历左子树 前序遍历右子树 总结:根左右 中序遍历 中序遍历左子树 访问根...

  • 二叉树知识(BST) 二叉查找树(Binary Search T

    二叉树基础知识总结 - CSDN博客 二叉树遍历分析 简单二叉树遍历,可分为:先序,中序,后序。 先序: 1.访问...

  • 二叉树遍历方式总结(递归&迭代)

    经过一周的学习,我了解到了二叉树的遍历是非常重要且容易出错的,所以对二叉树的遍历方式及实现方式做个总结 一. 遍历...

  • 二叉树 基础操作

    二叉树的使用 二叉树结构 先序创建二叉树 DFS 先序遍历二叉树 中序遍历二叉树 后序遍历二叉树 BFS 层次遍历...

  • 二叉树遍历

    二叉树 二叉树的存储结构 前序遍历 中序遍历 后序遍历 遍历代码 反转二叉树 深入学习二叉树 二叉树-你必须要懂!...

  • 二叉树操作

    树节点 逐行顺序解析二叉树 前序遍历二叉树 中序遍历二叉树 后序遍历二叉树 删除指定数值的节点 前序遍历顺序存储的...

  • 关于二叉树的算法题

    前序遍历中序遍历后序遍历判断是否是平衡二叉树判断是否是对称二叉树判断二叉树高度按照层遍历二叉树判断二叉树宽度

  • 二叉树三种遍历Swift代码实现

    二叉树的三种遍历 二叉树 前序遍历 中序遍历 后序遍历 另外 不得不说,得到二叉树的前序遍历和中序遍历的结果或者后...

网友评论

      本文标题:*二叉树的遍历总结

      本文链接:https://www.haomeiwen.com/subject/umeddhtx.html