递归的三要素:

  1. 递归函数的参数和返回值
  2. 单层递归的逻辑
  3. 终止条件

144.二叉树的先序遍历

题目链接:

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

题目描述:

给你二叉树的根节点 root ,返回它节点值的 前序 遍历。

输入:root = [1,null,2,3]
输出:[1,2,3]

思路:

二叉树的先序遍历指的是按照先遍历根节点,再遍历左子树,最后遍历右子树的顺序遍历二叉树。

具体的方法有递归和迭代两种写法。

递归方法:

var preorderTraversal = function(root) {
    let re = []
    firstOrder(root, re)
    return re
};
function preOrder(cur, list){
    if(!cur) return
    list.push(cur.val)
    firstOrder(cur.left, list)
    firstOrder(cur.right, list)
}

迭代方法:

利用栈后进先出的特性。开始时先将根节点入栈。接着设置一个循环,每一次循环都将栈顶节点弹出进行操作,之后依次将其右节点和左节点入栈。

var preorderTraversal = function(root) {
    if(!root) return []
    let re = []
    let stack = [root]
    while(stack.length > 0){
        let cur = stack.pop()
        re.push(cur.val)
        cur.right && stack.push(cur.right)
        cur.left && stack.push(cur.left)
    }
    return re
};

94.二叉树的中序遍历

题目链接:

https://leetcode.cn/problems/binary-tree-inorder-traversal/

题目描述:

给定一个二叉树的根节点 root ,返回 它的 中序 遍历

输入:root = [1,null,2,3]
输出:[1,3,2]

思路:

后序遍历就是先处理左子树,再处理根节点,最后处理右子树。

递归方法:

var inorderTraversal = function(root) {
    let re = []
    inOrder(root, re)
    return re
};
function inOrder(cur, list){
    if(!cur) return
    inOrder(cur.left, list)
    list.push(cur.val)
    inOrder(cur.right, list)
}

迭代方法:

迭代方法的关键就是显示构建和维护栈,可以类比递归来设计。

var inorderTraversal = function(root) {
    if(!root) return []
    let cur = root
    let stack = []
    let res = []
    while(stack.length || cur){
        if(cur){
            stack.push(cur)
            cur = cur.left
        }
        else{
            cur = stack.pop()
            res.push(cur.val)
            cur = cur.right
        }
    }
    return res
};

145.二叉树的后序遍历

题目链接:

https://leetcode.cn/problems/binary-tree-postorder-traversal/

题目描述:

给你一棵二叉树的根节点 root ,返回其节点值的 后序遍历

输入:root = [1,null,2,3]
输出:[3,2,1]

思路:

递归方法很简单,类似先序和中序遍历。但是迭代方法有一点复杂,复杂的点在于后序遍历处理节点的顺序是左-右-根,但是左右节点都是要由根节点获得,这代表会不止一次访问根节点,究竟哪一次访问根节点时对其进行处理就需要额外的条件来控制。

递归方法:

var postorderTraversal = function(root) {
    let re = []
    postOrder(root, re)
    return re
};
function postOrder(cur, list){
    if(!cur) return
    postOrder(cur.left, list)
    postOrder(cur.right, list)
    list.push(cur.val)
}

迭代方法:

我采取的方式是在每个节点入栈时增加一个flag。对某个节点的流程进行分析:这个节点会先入栈(root直接入栈,其他节点是作为父节点的子节点入栈,此时flag=0),之后的某次循环其作为栈顶节点被弹出(此时节点是作为根节点被访问到的),再将该节点和其右左子节点入栈(flag=1)。由于栈的性质,之后出栈符合后序的次序。

对整体流程来说就是:每次弹出节点时检查flag,为0说明这个节点是作为子节点被加入的,它的子节点没有被处理;为1则说明这个节点是作为根节点被加入的,它的子节点之前已经按照次序进栈出栈,现在该处理这个节点了(将其加入结果数组res中)

var postorderTraversal = function(root) {
    const stack = []
    let res = []
    if (!root) return []
    stack.push([root,0])
    while(stack.length) {
        const [node, flag] = stack.pop()
        if(flag) {
            res.push(node.val)
            continue;
        }
        stack.push([node, 1]) 
        if (node.right) stack.push([node.right, 0])
        if (node.left) stack.push([node.left, 0])
    };
    return res;
};