LeetCode 二叉树的遍历

430 阅读3分钟

「这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战

二叉树的遍历

二叉树的遍历主要有两种办法,一种是递归法,一种是迭代法,从理解的角度而言,我更喜欢递归的做法,更清晰明了。

递归法

最近在做二叉树相关的题目,发现用递归的方法比较多。我看力扣刷题指南里这样明确递归的写法

递归写法三要素

  1. 确定递归函数的参数和返回值:首先要明确哪些参数是需要在递归的过程里处理的,如果需要处理,就在递归函数里加上这个参数,还需要明确递归函数的返回值和返回类型
  2. 确定终止条件:如果不确定终止条件或者终止条件不对,系统会一直调用,内存栈必定溢出
  3. 确定单层递归的逻辑:确定每一层递归需要处理的信息
// 前序遍历递归法
var preorderTraversal = function(root) {
    let res = [];
    const dfs = function(root) {
        if (root === null) return;
        res.push(root.val);
        dfs(root.left);
        dfs(root.right);
    }
    dfs(root);
    return res;
}
// 中序遍历递归法
var inorderTraversal = function(root) {
    let res = [];
    const dfs = function(root) {
        if (root === null) return;
        dfs(root.left);
        res.push(root.val);
        dfs(root.right);
    }
    dfs(root);
    return res;
};
// 后序遍历递归法
var postorderTraversal = function(root) {
    let res = [];
    const dfs = function(root) {
        if (root === null) return;
        dfs(root.left);
        dfs(root.right);
        res.push(root.val);
    }
    dfs(root);
    return res;
};

以前序遍历为例子来确定递归三要素,确定递归函数的参数和返回值:因为我们要获取节点信息然后依照前序遍历打印,所以我们需要传入当前处理节点作为参数,也不需要什么返回值。确定终止条件:当当前节点为空时停止遍历,本层递归结束、确定单层递归逻辑:前序遍历是中左右,所以在单层的逻辑中,先取中节点的值压入结果栈中,然后依次对左子节点和右子节点进行递归遍历

迭代法

在我看来在二叉树的遍历这个问题上,迭代的逻辑是要比递归复杂的,我根据刷题指南里也用迭代法写了一遍二叉树遍历,第一次使用的迭代法,中序遍历和前序,后序的风格很不一样。然后吸取了别人的经验,我学会了二叉树遍历的统一迭代法

导致中序遍历和前序,后序的风格不一样的关键原因是,使用栈的时候,无法解决访问节点和处理节点(将元素放入结果集)不一致的问题,统一迭代法的思路是:将要访问的节点放入栈中,要处理的节点也放入栈中,但是对其加上标记。采用的标记方法是:将处理的节点放入栈之后,紧接着放入一个空指针做标记

// 统一迭代法
// 前序遍历:中左右
// 压栈顺序:右左中

var preorderTraversal = function(root, res = []) {
    const stack = [];
    if (root) stack.push(root);
    while(stack.length) {
        const node = stack.pop();
        if(!node) {
            res.push(stack.pop().val);
            continue;
        }
        if (node.right) stack.push(node.right); // 右
        if (node.left) stack.push(node.left); // 左
        stack.push(node); // 中
        stack.push(null);
    };
    return res;
};
//  中序遍历:左中右
//  压栈顺序:右中左
 
var inorderTraversal = function(root, res = []) {
    const stack = [];
    if (root) stack.push(root);
    while(stack.length) {
        const node = stack.pop();
        if(!node) {
            res.push(stack.pop().val);
            continue;
        }
        if (node.right) stack.push(node.right); // 右
        stack.push(node); // 中
        stack.push(null);
        if (node.left) stack.push(node.left); // 左
    };
    return res;
};
// 后续遍历:左右中
// 压栈顺序:中右左
 
var postorderTraversal = function(root, res = []) {
    const stack = [];
    if (root) stack.push(root);
    while(stack.length) {
        const node = stack.pop();
        if(!node) {
            res.push(stack.pop().val);
            continue;
        }
        stack.push(node); // 中
        stack.push(null);
        if (node.right) stack.push(node.right); // 右
        if (node.left) stack.push(node.left); // 左
    };
    return res;
};