树的前中后序

47 阅读2分钟

在处理树结构时,最基础也最重要的操作之一就是遍历。常见的遍历方式主要有三种:前序中序 和 后序(其实广义上还有层序遍历等,但这里聚焦于这三种深度优先遍历方式)。

使用 递归 实现这三种遍历非常直观且简单,但使用 非递归(即迭代)方式实现时,就需要借助栈结构,逻辑上会复杂一些,尤其是后序遍历。

🌱 递归实现

前序

lc-144

var preorderTraversal = function (root) {
    let res = [];
    var preorder = function (node) {
        if (!node) return;
        res.push(node.val);
        preorder(node.left);
        preorder(node.right);
    };
    preorder(root);
    return res;
};

中序

lc-94

var preorderTraversal = function (root) {
    let res = [];
    var preorder = function (node) {
        if (!node) return;
        preorder(node.left);
        res.push(node.val);
        preorder(node.right);
    };
    preorder(root);
    return res;
};

后序

lc-145

var postorderTraversal = function (root) {
    let res = [];
    var preorder = function (node) {
        if (!node) return;
        preorder(node.left);
        preorder(node.right);
        res.push(node.val);
    };
    preorder(root);
    return res;
};

从上面的解法可以看到,这三种解法,在进行值插入的地方有差异,这就是每个根节点的遍历位置。
前序:根左右
中序:左根右
后序:左右根

🔁 非递归实现

递归虽然直观,但在某些场景中并不适用(例如系统栈有限,或需要手动管理遍历过程)。非递归实现依赖栈来模拟递归调用过程。

前序

var preorderTraversal = function(root) {
    let res = []
    var preorder = function(node){
        let stack = []
        let current = node
        while(current || stack.length){
            while(current){
                res.push(current.val)
                stack.push(current)
                current = current.left
            }
            current = stack.pop()
            current = current.right
        }
    }
    preorder(root)
    return res
};

中序

var inorderTraversal = function (root) {
    let res = []
    let stack = []
    let current = root
    while (current || stack.length > 0) {
        while(current){
            stack.push(current)
            current = current.left
        }
        current = stack.pop()
        res.push(current.val)
        current = current.right
    }
    return res
};

后序

var postorderTraversal = function(root) {
    let res = []
    let current = root
    let stack = []
    let prev = null
    while(current || stack.length){
        while(current){
            stack.push(current)
            current = current.left
        }
        current = stack[stack.length - 1]
        if(!current.right || current.right === prev){
            res.push(current.val)
            stack.pop()
            prev = current
            current = null
        }else{
            current = current.right
        }
    }
    return res;
};

相较于前序和中序遍历,后序遍历的非递归实现更复杂,需要额外使用 prev 指针记录上一次访问的节点。因为在访问当前节点之前,需要确保其左右子树都已访问完成:

  • 如果当前节点没有右子树,或者右子树已经访问过(即 prev === current.right),就可以将当前节点加入结果集;
  • 否则,需要继续遍历右子树。